diff --git a/unknow-server-bench/src/main/java/unknow/server/bench/EncoderDecoder.java b/unknow-server-bench/src/main/java/unknow/server/bench/EncoderDecoder.java index 5348970f..51ce7c83 100644 --- a/unknow-server-bench/src/main/java/unknow/server/bench/EncoderDecoder.java +++ b/unknow-server-bench/src/main/java/unknow/server/bench/EncoderDecoder.java @@ -7,36 +7,226 @@ import java.nio.charset.StandardCharsets; import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.Param; +import org.openjdk.jmh.annotations.Scope; +import org.openjdk.jmh.annotations.Setup; +import org.openjdk.jmh.annotations.State; -import unknow.server.servlet.utils.Utf8Decoder; -import unknow.server.servlet.utils.Utf8Encoder; +import unknow.server.util.Decoder; +import unknow.server.util.Encoder; public class EncoderDecoder { - private static final String DATA = "Hello world!\nÇa va bien ?\nПривет, как дела?\n你好,世界\nこんにちは世界\n👋🌍✨🔥🚀\nLorem ipsum dolor sit amet, consectetur adipiscing elit."; + private static final String LATIN = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed non risus. Suspendisse lectus tortor, dignissim sit amet, adipiscing nec, ultricies sed, dolor. Cras elementum ultrices diam. Maecenas ligula massa, varius a, semper congue, euismod non, mi. Proin porttitor, orci nec nonummy molestie, enim est eleifend mi, non fermentum diam nisl sit amet erat. Duis semper. Duis arcu massa, scelerisque vitae, consequat in, pretium a, enim. Pellentesque congue. Ut in risus volutpat libero pharetra tempor. Cras vestibulum bibendum augue. Praesent egestas leo in pede. Praesent blandit odio eu enim. Pellentesque sed dui ut augue blandit sodales. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Aliquam nibh. Mauris ac mauris sed pede pellentesque fermentum. Maecenas adipiscing ante non diam sodales hendrerit.\r\n" + + "\r\n" + + "Ut velit mauris, egestas sed, gravida nec, ornare ut, mi. Aenean ut orci vel massa suscipit pulvinar. Nulla sollicitudin. Fusce varius, ligula non tempus aliquam, nunc turpis ullamcorper nibh, in tempus sapien eros vitae ligula. Pellentesque rhoncus nunc et augue. Integer id felis. Curabitur aliquet pellentesque diam. Integer quis metus vitae elit lobortis egestas. Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Morbi vel erat non mauris convallis vehicula. Nulla et sapien. Integer tortor tellus, aliquam faucibus, convallis id, congue eu, quam. Mauris ullamcorper felis vitae erat. Proin feugiat, augue non elementum posuere, metus purus iaculis lectus, et tristique ligula justo vitae magna.\r\n" + + "Aliquam convallis sollicitudin purus. Praesent aliquam, enim at fermentum mollis, ligula massa adipiscing nisl, ac euismod nibh nisl eu lectus. Fusce vulputate sem at sapien. Vivamus leo. Aliquam euismod libero eu enim. Nulla nec felis sed leo placerat imperdiet. Aenean suscipit nulla in justo. Suspendisse cursus rutrum augue. Nulla tincidunt tincidunt mi. Curabitur iaculis, lorem vel rhoncus faucibus, felis magna fermentum augue, et ultricies lacus lorem varius purus. Curabitur eu amet." + + "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed non risus. Suspendisse lectus tortor, dignissim sit amet, adipiscing nec, ultricies sed, dolor. Cras elementum ultrices diam. Maecenas ligula massa, varius a, semper congue, euismod non, mi. Proin porttitor, orci nec nonummy molestie, enim est eleifend mi, non fermentum diam nisl sit amet erat. Duis semper. Duis arcu massa, scelerisque vitae, consequat in, pretium a, enim. Pellentesque congue. Ut in risus volutpat libero pharetra tempor. Cras vestibulum bibendum augue. Praesent egestas leo in pede. Praesent blandit odio eu enim. Pellentesque sed dui ut augue blandit sodales. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Aliquam nibh. Mauris ac mauris sed pede pellentesque fermentum. Maecenas adipiscing ante non diam sodales hendrerit.\r\n" + + "\r\n" + + "Ut velit mauris, egestas sed, gravida nec, ornare ut, mi. Aenean ut orci vel massa suscipit pulvinar. Nulla sollicitudin. Fusce varius, ligula non tempus aliquam, nunc turpis ullamcorper nibh, in tempus sapien eros vitae ligula. Pellentesque rhoncus nunc et augue. Integer id felis. Curabitur aliquet pellentesque diam. Integer quis metus vitae elit lobortis egestas. Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Morbi vel erat non mauris convallis vehicula. Nulla et sapien. Integer tortor tellus, aliquam faucibus, convallis id, congue eu, quam. Mauris ullamcorper felis vitae erat. Proin feugiat, augue non elementum posuere, metus purus iaculis lectus, et tristique ligula justo vitae magna.\r\n" + + "Aliquam convallis sollicitudin purus. Praesent aliquam, enim at fermentum mollis, ligula massa adipiscing nisl, ac euismod nibh nisl eu lectus. Fusce vulputate sem at sapien. Vivamus leo. Aliquam euismod libero eu enim. Nulla nec felis sed leo placerat imperdiet. Aenean suscipit nulla in justo. Suspendisse cursus rutrum augue. Nulla tincidunt tincidunt mi. Curabitur iaculis, lorem vel rhoncus faucibus, felis magna fermentum augue, et ultricies lacus lorem varius purus. Curabitur eu amet." + + "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed non risus. Suspendisse lectus tortor, dignissim sit amet, adipiscing nec, ultricies sed, dolor. Cras elementum ultrices diam. Maecenas ligula massa, varius a, semper congue, euismod non, mi. Proin porttitor, orci nec nonummy molestie, enim est eleifend mi, non fermentum diam nisl sit amet erat. Duis semper. Duis arcu massa, scelerisque vitae, consequat in, pretium a, enim. Pellentesque congue. Ut in risus volutpat libero pharetra tempor. Cras vestibulum bibendum augue. Praesent egestas leo in pede. Praesent blandit odio eu enim. Pellentesque sed dui ut augue blandit sodales. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Aliquam nibh. Mauris ac mauris sed pede pellentesque fermentum. Maecenas adipiscing ante non diam sodales hendrerit.\r\n" + + "\r\n" + + "Ut velit mauris, egestas sed, gravida nec, ornare ut, mi. Aenean ut orci vel massa suscipit pulvinar. Nulla sollicitudin. Fusce varius, ligula non tempus aliquam, nunc turpis ullamcorper nibh, in tempus sapien eros vitae ligula. Pellentesque rhoncus nunc et augue. Integer id felis. Curabitur aliquet pellentesque diam. Integer quis metus vitae elit lobortis egestas. Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Morbi vel erat non mauris convallis vehicula. Nulla et sapien. Integer tortor tellus, aliquam faucibus, convallis id, congue eu, quam. Mauris ullamcorper felis vitae erat. Proin feugiat, augue non elementum posuere, metus purus iaculis lectus, et tristique ligula justo vitae magna.\r\n" + + "Aliquam convallis sollicitudin purus. Praesent aliquam, enim at fermentum mollis, ligula massa adipiscing nisl, ac euismod nibh nisl eu lectus. Fusce vulputate sem at sapien. Vivamus leo. Aliquam euismod libero eu enim. Nulla nec felis sed leo placerat imperdiet. Aenean suscipit nulla in justo. Suspendisse cursus rutrum augue. Nulla tincidunt tincidunt mi. Curabitur iaculis, lorem vel rhoncus faucibus, felis magna fermentum augue, et ultricies lacus lorem varius purus. Curabitur eu amet." + + "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed non risus. Suspendisse lectus tortor, dignissim sit amet, adipiscing nec, ultricies sed, dolor. Cras elementum ultrices diam. Maecenas ligula massa, varius a, semper congue, euismod non, mi. Proin porttitor, orci nec nonummy molestie, enim est eleifend mi, non fermentum diam nisl sit amet erat. Duis semper. Duis arcu massa, scelerisque vitae, consequat in, pretium a, enim. Pellentesque congue. Ut in risus volutpat libero pharetra tempor. Cras vestibulum bibendum augue. Praesent egestas leo in pede. Praesent blandit odio eu enim. Pellentesque sed dui ut augue blandit sodales. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Aliquam nibh. Mauris ac mauris sed pede pellentesque fermentum. Maecenas adipiscing ante non diam sodales hendrerit.\r\n" + + "\r\n" + + "Ut velit mauris, egestas sed, gravida nec, ornare ut, mi. Aenean ut orci vel massa suscipit pulvinar. Nulla sollicitudin. Fusce varius, ligula non tempus aliquam, nunc turpis ullamcorper nibh, in tempus sapien eros vitae ligula. Pellentesque rhoncus nunc et augue. Integer id felis. Curabitur aliquet pellentesque diam. Integer quis metus vitae elit lobortis egestas. Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Morbi vel erat non mauris convallis vehicula. Nulla et sapien. Integer tortor tellus, aliquam faucibus, convallis id, congue eu, quam. Mauris ullamcorper felis vitae erat. Proin feugiat, augue non elementum posuere, metus purus iaculis lectus, et tristique ligula justo vitae magna.\r\n" + + "Aliquam convallis sollicitudin purus. Praesent aliquam, enim at fermentum mollis, ligula massa adipiscing nisl, ac euismod nibh nisl eu lectus. Fusce vulputate sem at sapien. Vivamus leo. Aliquam euismod libero eu enim. Nulla nec felis sed leo placerat imperdiet. Aenean suscipit nulla in justo. Suspendisse cursus rutrum augue. Nulla tincidunt tincidunt mi. Curabitur iaculis, lorem vel rhoncus faucibus, felis magna fermentum augue, et ultricies lacus lorem varius purus. Curabitur eu amet." + + "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed non risus. Suspendisse lectus tortor, dignissim sit amet, adipiscing nec, ultricies sed, dolor. Cras elementum ultrices diam. Maecenas ligula massa, varius a, semper congue, euismod non, mi. Proin porttitor, orci nec nonummy molestie, enim est eleifend mi, non fermentum diam nisl sit amet erat. Duis semper. Duis arcu massa, scelerisque vitae, consequat in, pretium a, enim. Pellentesque congue. Ut in risus volutpat libero pharetra tempor. Cras vestibulum bibendum augue. Praesent egestas leo in pede. Praesent blandit odio eu enim. Pellentesque sed dui ut augue blandit sodales. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Aliquam nibh. Mauris ac mauris sed pede pellentesque fermentum. Maecenas adipiscing ante non diam sodales hendrerit.\r\n" + + "\r\n" + + "Ut velit mauris, egestas sed, gravida nec, ornare ut, mi. Aenean ut orci vel massa suscipit pulvinar. Nulla sollicitudin. Fusce varius, ligula non tempus aliquam, nunc turpis ullamcorper nibh, in tempus sapien eros vitae ligula. Pellentesque rhoncus nunc et augue. Integer id felis. Curabitur aliquet pellentesque diam. Integer quis metus vitae elit lobortis egestas. Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Morbi vel erat non mauris convallis vehicula. Nulla et sapien. Integer tortor tellus, aliquam faucibus, convallis id, congue eu, quam. Mauris ullamcorper felis vitae erat. Proin feugiat, augue non elementum posuere, metus purus iaculis lectus, et tristique ligula justo vitae magna.\r\n" + + "Aliquam convallis sollicitudin purus. Praesent aliquam, enim at fermentum mollis, ligula massa adipiscing nisl, ac euismod nibh nisl eu lectus. Fusce vulputate sem at sapien. Vivamus leo. Aliquam euismod libero eu enim. Nulla nec felis sed leo placerat imperdiet. Aenean suscipit nulla in justo. Suspendisse cursus rutrum augue. Nulla tincidunt tincidunt mi. Curabitur iaculis, lorem vel rhoncus faucibus, felis magna fermentum augue, et ultricies lacus lorem varius purus. Curabitur eu amet." + + "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed non risus. Suspendisse lectus tortor, dignissim sit amet, adipiscing nec, ultricies sed, dolor. Cras elementum ultrices diam. Maecenas ligula massa, varius a, semper congue, euismod non, mi. Proin porttitor, orci nec nonummy molestie, enim est eleifend mi, non fermentum diam nisl sit amet erat. Duis semper. Duis arcu massa, scelerisque vitae, consequat in, pretium a, enim. Pellentesque congue. Ut in risus volutpat libero pharetra tempor. Cras vestibulum bibendum augue. Praesent egestas leo in pede. Praesent blandit odio eu enim. Pellentesque sed dui ut augue blandit sodales. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Aliquam nibh. Mauris ac mauris sed pede pellentesque fermentum. Maecenas adipiscing ante non diam sodales hendrerit.\r\n" + + "\r\n" + + "Ut velit mauris, egestas sed, gravida nec, ornare ut, mi. Aenean ut orci vel massa suscipit pulvinar. Nulla sollicitudin. Fusce varius, ligula non tempus aliquam, nunc turpis ullamcorper nibh, in tempus sapien eros vitae ligula. Pellentesque rhoncus nunc et augue. Integer id felis. Curabitur aliquet pellentesque diam. Integer quis metus vitae elit lobortis egestas. Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Morbi vel erat non mauris convallis vehicula. Nulla et sapien. Integer tortor tellus, aliquam faucibus, convallis id, congue eu, quam. Mauris ullamcorper felis vitae erat. Proin feugiat, augue non elementum posuere, metus purus iaculis lectus, et tristique ligula justo vitae magna.\r\n" + + "Aliquam convallis sollicitudin purus. Praesent aliquam, enim at fermentum mollis, ligula massa adipiscing nisl, ac euismod nibh nisl eu lectus. Fusce vulputate sem at sapien. Vivamus leo. Aliquam euismod libero eu enim. Nulla nec felis sed leo placerat imperdiet. Aenean suscipit nulla in justo. Suspendisse cursus rutrum augue. Nulla tincidunt tincidunt mi. Curabitur iaculis, lorem vel rhoncus faucibus, felis magna fermentum augue, et ultricies lacus lorem varius purus. Curabitur eu amet." + + "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed non risus. Suspendisse lectus tortor, dignissim sit amet, adipiscing nec, ultricies sed, dolor. Cras elementum ultrices diam. Maecenas ligula massa, varius a, semper congue, euismod non, mi. Proin porttitor, orci nec nonummy molestie, enim est eleifend mi, non fermentum diam nisl sit amet erat. Duis semper. Duis arcu massa, scelerisque vitae, consequat in, pretium a, enim. Pellentesque congue. Ut in risus volutpat libero pharetra tempor. Cras vestibulum bibendum augue. Praesent egestas leo in pede. Praesent blandit odio eu enim. Pellentesque sed dui ut augue blandit sodales. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Aliquam nibh. Mauris ac mauris sed pede pellentesque fermentum. Maecenas adipiscing ante non diam sodales hendrerit.\r\n" + + "\r\n" + + "Ut velit mauris, egestas sed, gravida nec, ornare ut, mi. Aenean ut orci vel massa suscipit pulvinar. Nulla sollicitudin. Fusce varius, ligula non tempus aliquam, nunc turpis ullamcorper nibh, in tempus sapien eros vitae ligula. Pellentesque rhoncus nunc et augue. Integer id felis. Curabitur aliquet pellentesque diam. Integer quis metus vitae elit lobortis egestas. Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Morbi vel erat non mauris convallis vehicula. Nulla et sapien. Integer tortor tellus, aliquam faucibus, convallis id, congue eu, quam. Mauris ullamcorper felis vitae erat. Proin feugiat, augue non elementum posuere, metus purus iaculis lectus, et tristique ligula justo vitae magna.\r\n" + + "Aliquam convallis sollicitudin purus. Praesent aliquam, enim at fermentum mollis, ligula massa adipiscing nisl, ac euismod nibh nisl eu lectus. Fusce vulputate sem at sapien. Vivamus leo. Aliquam euismod libero eu enim. Nulla nec felis sed leo placerat imperdiet. Aenean suscipit nulla in justo. Suspendisse cursus rutrum augue. Nulla tincidunt tincidunt mi. Curabitur iaculis, lorem vel rhoncus faucibus, felis magna fermentum augue, et ultricies lacus lorem varius purus. Curabitur eu amet." + + "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed non risus. Suspendisse lectus tortor, dignissim sit amet, adipiscing nec, ultricies sed, dolor. Cras elementum ultrices diam. Maecenas ligula massa, varius a, semper congue, euismod non, mi. Proin porttitor, orci nec nonummy molestie, enim est eleifend mi, non fermentum diam nisl sit amet erat. Duis semper. Duis arcu massa, scelerisque vitae, consequat in, pretium a, enim. Pellentesque congue. Ut in risus volutpat libero pharetra tempor. Cras vestibulum bibendum augue. Praesent egestas leo in pede. Praesent blandit odio eu enim. Pellentesque sed dui ut augue blandit sodales. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Aliquam nibh. Mauris ac mauris sed pede pellentesque fermentum. Maecenas adipiscing ante non diam sodales hendrerit.\r\n" + + "\r\n" + + "Ut velit mauris, egestas sed, gravida nec, ornare ut, mi. Aenean ut orci vel massa suscipit pulvinar. Nulla sollicitudin. Fusce varius, ligula non tempus aliquam, nunc turpis ullamcorper nibh, in tempus sapien eros vitae ligula. Pellentesque rhoncus nunc et augue. Integer id felis. Curabitur aliquet pellentesque diam. Integer quis metus vitae elit lobortis egestas. Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Morbi vel erat non mauris convallis vehicula. Nulla et sapien. Integer tortor tellus, aliquam faucibus, convallis id, congue eu, quam. Mauris ullamcorper felis vitae erat. Proin feugiat, augue non elementum posuere, metus purus iaculis lectus, et tristique ligula justo vitae magna.\r\n" + + "Aliquam convallis sollicitudin purus. Praesent aliquam, enim at fermentum mollis, ligula massa adipiscing nisl, ac euismod nibh nisl eu lectus. Fusce vulputate sem at sapien. Vivamus leo. Aliquam euismod libero eu enim. Nulla nec felis sed leo placerat imperdiet. Aenean suscipit nulla in justo. Suspendisse cursus rutrum augue. Nulla tincidunt tincidunt mi. Curabitur iaculis, lorem vel rhoncus faucibus, felis magna fermentum augue, et ultricies lacus lorem varius purus. Curabitur eu amet." + + "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed non risus. Suspendisse lectus tortor, dignissim sit amet, adipiscing nec, ultricies sed, dolor. Cras elementum ultrices diam. Maecenas ligula massa, varius a, semper congue, euismod non, mi. Proin porttitor, orci nec nonummy molestie, enim est eleifend mi, non fermentum diam nisl sit amet erat. Duis semper. Duis arcu massa, scelerisque vitae, consequat in, pretium a, enim. Pellentesque congue. Ut in risus volutpat libero pharetra tempor. Cras vestibulum bibendum augue. Praesent egestas leo in pede. Praesent blandit odio eu enim. Pellentesque sed dui ut augue blandit sodales. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Aliquam nibh. Mauris ac mauris sed pede pellentesque fermentum. Maecenas adipiscing ante non diam sodales hendrerit.\r\n" + + "\r\n" + + "Ut velit mauris, egestas sed, gravida nec, ornare ut, mi. Aenean ut orci vel massa suscipit pulvinar. Nulla sollicitudin. Fusce varius, ligula non tempus aliquam, nunc turpis ullamcorper nibh, in tempus sapien eros vitae ligula. Pellentesque rhoncus nunc et augue. Integer id felis. Curabitur aliquet pellentesque diam. Integer quis metus vitae elit lobortis egestas. Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Morbi vel erat non mauris convallis vehicula. Nulla et sapien. Integer tortor tellus, aliquam faucibus, convallis id, congue eu, quam. Mauris ullamcorper felis vitae erat. Proin feugiat, augue non elementum posuere, metus purus iaculis lectus, et tristique ligula justo vitae magna.\r\n" + + "Aliquam convallis sollicitudin purus. Praesent aliquam, enim at fermentum mollis, ligula massa adipiscing nisl, ac euismod nibh nisl eu lectus. Fusce vulputate sem at sapien. Vivamus leo. Aliquam euismod libero eu enim. Nulla nec felis sed leo placerat imperdiet. Aenean suscipit nulla in justo. Suspendisse cursus rutrum augue. Nulla tincidunt tincidunt mi. Curabitur iaculis, lorem vel rhoncus faucibus, felis magna fermentum augue, et ultricies lacus lorem varius purus. Curabitur eu amet." + + "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed non risus. Suspendisse lectus tortor, dignissim sit amet, adipiscing nec, ultricies sed, dolor. Cras elementum ultrices diam. Maecenas ligula massa, varius a, semper congue, euismod non, mi. Proin porttitor, orci nec nonummy molestie, enim est eleifend mi, non fermentum diam nisl sit amet erat. Duis semper. Duis arcu massa, scelerisque vitae, consequat in, pretium a, enim. Pellentesque congue. Ut in risus volutpat libero pharetra tempor. Cras vestibulum bibendum augue. Praesent egestas leo in pede. Praesent blandit odio eu enim. Pellentesque sed dui ut augue blandit sodales. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Aliquam nibh. Mauris ac mauris sed pede pellentesque fermentum. Maecenas adipiscing ante non diam sodales hendrerit.\r\n" + + "\r\n" + + "Ut velit mauris, egestas sed, gravida nec, ornare ut, mi. Aenean ut orci vel massa suscipit pulvinar. Nulla sollicitudin. Fusce varius, ligula non tempus aliquam, nunc turpis ullamcorper nibh, in tempus sapien eros vitae ligula. Pellentesque rhoncus nunc et augue. Integer id felis. Curabitur aliquet pellentesque diam. Integer quis metus vitae elit lobortis egestas. Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Morbi vel erat non mauris convallis vehicula. Nulla et sapien. Integer tortor tellus, aliquam faucibus, convallis id, congue eu, quam. Mauris ullamcorper felis vitae erat. Proin feugiat, augue non elementum posuere, metus purus iaculis lectus, et tristique ligula justo vitae magna.\r\n" + + "Aliquam convallis sollicitudin purus. Praesent aliquam, enim at fermentum mollis, ligula massa adipiscing nisl, ac euismod nibh nisl eu lectus. Fusce vulputate sem at sapien. Vivamus leo. Aliquam euismod libero eu enim. Nulla nec felis sed leo placerat imperdiet. Aenean suscipit nulla in justo. Suspendisse cursus rutrum augue. Nulla tincidunt tincidunt mi. Curabitur iaculis, lorem vel rhoncus faucibus, felis magna fermentum augue, et ultricies lacus lorem varius purus. Curabitur eu amet." + + "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed non risus. Suspendisse lectus tortor, dignissim sit amet, adipiscing nec, ultricies sed, dolor. Cras elementum ultrices diam. Maecenas ligula massa, varius a, semper congue, euismod non, mi. Proin porttitor, orci nec nonummy molestie, enim est eleifend mi, non fermentum diam nisl sit amet erat. Duis semper. Duis arcu massa, scelerisque vitae, consequat in, pretium a, enim. Pellentesque congue. Ut in risus volutpat libero pharetra tempor. Cras vestibulum bibendum augue. Praesent egestas leo in pede. Praesent blandit odio eu enim. Pellentesque sed dui ut augue blandit sodales. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Aliquam nibh. Mauris ac mauris sed pede pellentesque fermentum. Maecenas adipiscing ante non diam sodales hendrerit.\r\n" + + "\r\n" + + "Ut velit mauris, egestas sed, gravida nec, ornare ut, mi. Aenean ut orci vel massa suscipit pulvinar. Nulla sollicitudin. Fusce varius, ligula non tempus aliquam, nunc turpis ullamcorper nibh, in tempus sapien eros vitae ligula. Pellentesque rhoncus nunc et augue. Integer id felis. Curabitur aliquet pellentesque diam. Integer quis metus vitae elit lobortis egestas. Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Morbi vel erat non mauris convallis vehicula. Nulla et sapien. Integer tortor tellus, aliquam faucibus, convallis id, congue eu, quam. Mauris ullamcorper felis vitae erat. Proin feugiat, augue non elementum posuere, metus purus iaculis lectus, et tristique ligula justo vitae magna.\r\n" + + "Aliquam convallis sollicitudin purus. Praesent aliquam, enim at fermentum mollis, ligula massa adipiscing nisl, ac euismod nibh nisl eu lectus. Fusce vulputate sem at sapien. Vivamus leo. Aliquam euismod libero eu enim. Nulla nec felis sed leo placerat imperdiet. Aenean suscipit nulla in justo. Suspendisse cursus rutrum augue. Nulla tincidunt tincidunt mi. Curabitur iaculis, lorem vel rhoncus faucibus, felis magna fermentum augue, et ultricies lacus lorem varius purus. Curabitur eu amet." + + "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed non risus. Suspendisse lectus tortor, dignissim sit amet, adipiscing nec, ultricies sed, dolor. Cras elementum ultrices diam. Maecenas ligula massa, varius a, semper congue, euismod non, mi. Proin porttitor, orci nec nonummy molestie, enim est eleifend mi, non fermentum diam nisl sit amet erat. Duis semper. Duis arcu massa, scelerisque vitae, consequat in, pretium a, enim. Pellentesque congue. Ut in risus volutpat libero pharetra tempor. Cras vestibulum bibendum augue. Praesent egestas leo in pede. Praesent blandit odio eu enim. Pellentesque sed dui ut augue blandit sodales. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Aliquam nibh. Mauris ac mauris sed pede pellentesque fermentum. Maecenas adipiscing ante non diam sodales hendrerit.\r\n" + + "\r\n" + + "Ut velit mauris, egestas sed, gravida nec, ornare ut, mi. Aenean ut orci vel massa suscipit pulvinar. Nulla sollicitudin. Fusce varius, ligula non tempus aliquam, nunc turpis ullamcorper nibh, in tempus sapien eros vitae ligula. Pellentesque rhoncus nunc et augue. Integer id felis. Curabitur aliquet pellentesque diam. Integer quis metus vitae elit lobortis egestas. Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Morbi vel erat non mauris convallis vehicula. Nulla et sapien. Integer tortor tellus, aliquam faucibus, convallis id, congue eu, quam. Mauris ullamcorper felis vitae erat. Proin feugiat, augue non elementum posuere, metus purus iaculis lectus, et tristique ligula justo vitae magna.\r\n" + + "Aliquam convallis sollicitudin purus. Praesent aliquam, enim at fermentum mollis, ligula massa adipiscing nisl, ac euismod nibh nisl eu lectus. Fusce vulputate sem at sapien. Vivamus leo. Aliquam euismod libero eu enim. Nulla nec felis sed leo placerat imperdiet. Aenean suscipit nulla in justo. Suspendisse cursus rutrum augue. Nulla tincidunt tincidunt mi. Curabitur iaculis, lorem vel rhoncus faucibus, felis magna fermentum augue, et ultricies lacus lorem varius purus. Curabitur eu amet."; + + private static final String SIMPLE = "Hello world!\nÇa va bien ?\nПривет, как дела?\n你好,世界\nこんにちは世界\n👋🌍✨🔥🚀\nLorem ipsum dolor sit amet, consectetur adipiscing elit."; + + private static final String COMPLEX = "𓂀𓆣𓇼𓁹𓃰 𐍈𐌰𐌽𐌳𐌴𐍂 🌌✨🌀💫 🌍🌎🌏 🧬⚛️🔮 ∑∏√∞ ≈≠≡ ⌘⌬⌭ ⟁\r\n" + "如来如去 如夢如幻 🌸🌺🌼 美しい世界 🌈🌠 🌕🌖🌗🌘\r\n" + + "العربية العربية العربية ✨🌙⭐ ☯️☪️✡️ 🕉️ 🔱\r\n" + "\r\n" + "🐉🦄🐲🐾 🐉🦄🐲 \uD83E\uDDFF\uD83D\uDC41\uFE0F\u200D\uD83D\uDDE8\uFE0F 👁️👁️ 🌀🌀🌀\r\n" + + "\uD83D\uDC68\u200D\uD83D\uDE80\uD83D\uDC69\u200D\uD83D\uDE80\uD83E\uDDD1\u200D\uD83D\uDE80 🚀🛸🚀🛸 🌌🌌🌌 💫💫💫\r\n" + "\r\n" + "𒀱𒂗𒆠 𒀭𒈹𒂊𒉡 𒌷𒆠𒂗𒄀 🏺🏺🏺\r\n" + "ᚠᛇᚻᚢᚦᚨᚱᚲ ⚔️🛡️ ⚔️🛡️\r\n" + "𑀓𑀺𑀢𑁆𑀢𑀺 𑀧𑀭𑀫𑀸𑀦 🌿🌿🌿\r\n" + + "\r\n" + "🌲🍃🌲🍃🌲 🍄🍄🍄 🌸🌸🌸\r\n" + "🌊🌊🌊 🌊🌊🌊 🐚🐚🐚 🐠🐟🐡\r\n" + "\r\n" + "💻📡📶 🧠🤖🧠 🤖🧠🤖 ⚙️⚙️⚙️\r\n" + "0101 ✨ 𐍈 0101 ✨ 𐍈\r\n" + "\r\n" + + "ქართული ქართული ქართული 🌄🌄\r\n" + "संस्कृतम् संस्कृतम् संस्कृतम् 🔱🔱\r\n" + "\r\n" + "🔥🔥🔥 ❄️❄️❄️ ⚡⚡⚡ 🌪️🌪️🌪️\r\n" + "❤️🧡💛💚💙💜🖤 🤍🤎 💖💗💓\r\n" + + "\r\n" + "🌐🌐🌐 🌐🌐🌐 🌀🌀🌀 🌀🌀🌀\r\n" + "💫✨🌟⭐✨💫 🌟✨⭐💫✨🌟\r\n" + "\r\n" + "𓂀𓂀𓂀 𓇼𓇼𓇼 𓆣𓆣𓆣\r\n" + "𐍈𐍈𐍈 ᚠᚠᚠ 𒀱𒀱𒀱" + + "𓂀𓆣𓇼𓁹𓃰 𐍈𐌰𐌽𐌳𐌴𐍂 🌌✨🌀💫 🌍🌎🌏 🧬⚛️🔮 ∑∏√∞ ≈≠≡ ⌘⌬⌭ ⟁\r\n" + "如来如去 如夢如幻 🌸🌺🌼 美しい世界 🌈🌠 🌕🌖🌗🌘\r\n" + + "العربية العربية العربية ✨🌙⭐ ☯️☪️✡️ 🕉️ 🔱\r\n" + "\r\n" + "🐉🦄🐲🐾 🐉🦄🐲 \uD83E\uDDFF\uD83D\uDC41\uFE0F\u200D\uD83D\uDDE8\uFE0F 👁️👁️ 🌀🌀🌀\r\n" + + "\uD83D\uDC68\u200D\uD83D\uDE80\uD83D\uDC69\u200D\uD83D\uDE80\uD83E\uDDD1\u200D\uD83D\uDE80 🚀🛸🚀🛸 🌌🌌🌌 💫💫💫\r\n" + "\r\n" + "𒀱𒂗𒆠 𒀭𒈹𒂊𒉡 𒌷𒆠𒂗𒄀 🏺🏺🏺\r\n" + "ᚠᛇᚻᚢᚦᚨᚱᚲ ⚔️🛡️ ⚔️🛡️\r\n" + "𑀓𑀺𑀢𑁆𑀢𑀺 𑀧𑀭𑀫𑀸𑀦 🌿🌿🌿\r\n" + + "\r\n" + "🌲🍃🌲🍃🌲 🍄🍄🍄 🌸🌸🌸\r\n" + "🌊🌊🌊 🌊🌊🌊 🐚🐚🐚 🐠🐟🐡\r\n" + "\r\n" + "💻📡📶 🧠🤖🧠 🤖🧠🤖 ⚙️⚙️⚙️\r\n" + "0101 ✨ 𐍈 0101 ✨ 𐍈\r\n" + "\r\n" + + "ქართული ქართული ქართული 🌄🌄\r\n" + "संस्कृतम् संस्कृतम् संस्कृतम् 🔱🔱\r\n" + "\r\n" + "🔥🔥🔥 ❄️❄️❄️ ⚡⚡⚡ 🌪️🌪️🌪️\r\n" + "❤️🧡💛💚💙💜🖤 🤍🤎 💖💗💓\r\n" + + "\r\n" + "🌐🌐🌐 🌐🌐🌐 🌀🌀🌀 🌀🌀🌀\r\n" + "💫✨🌟⭐✨💫 🌟✨⭐💫✨🌟\r\n" + "\r\n" + "𓂀𓂀𓂀 𓇼𓇼𓇼 𓆣𓆣𓆣\r\n" + "𐍈𐍈𐍈 ᚠᚠᚠ 𒀱𒀱𒀱" + + "𓂀𓆣𓇼𓁹𓃰 𐍈𐌰𐌽𐌳𐌴𐍂 🌌✨🌀💫 🌍🌎🌏 🧬⚛️🔮 ∑∏√∞ ≈≠≡ ⌘⌬⌭ ⟁\r\n" + "如来如去 如夢如幻 🌸🌺🌼 美しい世界 🌈🌠 🌕🌖🌗🌘\r\n" + + "العربية العربية العربية ✨🌙⭐ ☯️☪️✡️ 🕉️ 🔱\r\n" + "\r\n" + "🐉🦄🐲🐾 🐉🦄🐲 \uD83E\uDDFF\uD83D\uDC41\uFE0F\u200D\uD83D\uDDE8\uFE0F 👁️👁️ 🌀🌀🌀\r\n" + + "\uD83D\uDC68\u200D\uD83D\uDE80\uD83D\uDC69\u200D\uD83D\uDE80\uD83E\uDDD1\u200D\uD83D\uDE80 🚀🛸🚀🛸 🌌🌌🌌 💫💫💫\r\n" + "\r\n" + "𒀱𒂗𒆠 𒀭𒈹𒂊𒉡 𒌷𒆠𒂗𒄀 🏺🏺🏺\r\n" + "ᚠᛇᚻᚢᚦᚨᚱᚲ ⚔️🛡️ ⚔️🛡️\r\n" + "𑀓𑀺𑀢𑁆𑀢𑀺 𑀧𑀭𑀫𑀸𑀦 🌿🌿🌿\r\n" + + "\r\n" + "🌲🍃🌲🍃🌲 🍄🍄🍄 🌸🌸🌸\r\n" + "🌊🌊🌊 🌊🌊🌊 🐚🐚🐚 🐠🐟🐡\r\n" + "\r\n" + "💻📡📶 🧠🤖🧠 🤖🧠🤖 ⚙️⚙️⚙️\r\n" + "0101 ✨ 𐍈 0101 ✨ 𐍈\r\n" + "\r\n" + + "ქართული ქართული ქართული 🌄🌄\r\n" + "संस्कृतम् संस्कृतम् संस्कृतम् 🔱🔱\r\n" + "\r\n" + "🔥🔥🔥 ❄️❄️❄️ ⚡⚡⚡ 🌪️🌪️🌪️\r\n" + "❤️🧡💛💚💙💜🖤 🤍🤎 💖💗💓\r\n" + + "\r\n" + "🌐🌐🌐 🌐🌐🌐 🌀🌀🌀 🌀🌀🌀\r\n" + "💫✨🌟⭐✨💫 🌟✨⭐💫✨🌟\r\n" + "\r\n" + "𓂀𓂀𓂀 𓇼𓇼𓇼 𓆣𓆣𓆣\r\n" + "𐍈𐍈𐍈 ᚠᚠᚠ 𒀱𒀱𒀱" + + "𓂀𓆣𓇼𓁹𓃰 𐍈𐌰𐌽𐌳𐌴𐍂 🌌✨🌀💫 🌍🌎🌏 🧬⚛️🔮 ∑∏√∞ ≈≠≡ ⌘⌬⌭ ⟁\r\n" + "如来如去 如夢如幻 🌸🌺🌼 美しい世界 🌈🌠 🌕🌖🌗🌘\r\n" + + "العربية العربية العربية ✨🌙⭐ ☯️☪️✡️ 🕉️ 🔱\r\n" + "\r\n" + "🐉🦄🐲🐾 🐉🦄🐲 \uD83E\uDDFF\uD83D\uDC41\uFE0F\u200D\uD83D\uDDE8\uFE0F 👁️👁️ 🌀🌀🌀\r\n" + + "\uD83D\uDC68\u200D\uD83D\uDE80\uD83D\uDC69\u200D\uD83D\uDE80\uD83E\uDDD1\u200D\uD83D\uDE80 🚀🛸🚀🛸 🌌🌌🌌 💫💫💫\r\n" + "\r\n" + "𒀱𒂗𒆠 𒀭𒈹𒂊𒉡 𒌷𒆠𒂗𒄀 🏺🏺🏺\r\n" + "ᚠᛇᚻᚢᚦᚨᚱᚲ ⚔️🛡️ ⚔️🛡️\r\n" + "𑀓𑀺𑀢𑁆𑀢𑀺 𑀧𑀭𑀫𑀸𑀦 🌿🌿🌿\r\n" + + "\r\n" + "🌲🍃🌲🍃🌲 🍄🍄🍄 🌸🌸🌸\r\n" + "🌊🌊🌊 🌊🌊🌊 🐚🐚🐚 🐠🐟🐡\r\n" + "\r\n" + "💻📡📶 🧠🤖🧠 🤖🧠🤖 ⚙️⚙️⚙️\r\n" + "0101 ✨ 𐍈 0101 ✨ 𐍈\r\n" + "\r\n" + + "ქართული ქართული ქართული 🌄🌄\r\n" + "संस्कृतम् संस्कृतम् संस्कृतम् 🔱🔱\r\n" + "\r\n" + "🔥🔥🔥 ❄️❄️❄️ ⚡⚡⚡ 🌪️🌪️🌪️\r\n" + "❤️🧡💛💚💙💜🖤 🤍🤎 💖💗💓\r\n" + + "\r\n" + "🌐🌐🌐 🌐🌐🌐 🌀🌀🌀 🌀🌀🌀\r\n" + "💫✨🌟⭐✨💫 🌟✨⭐💫✨🌟\r\n" + "\r\n" + "𓂀𓂀𓂀 𓇼𓇼𓇼 𓆣𓆣𓆣\r\n" + "𐍈𐍈𐍈 ᚠᚠᚠ 𒀱𒀱𒀱" + + "𓂀𓆣𓇼𓁹𓃰 𐍈𐌰𐌽𐌳𐌴𐍂 🌌✨🌀💫 🌍🌎🌏 🧬⚛️🔮 ∑∏√∞ ≈≠≡ ⌘⌬⌭ ⟁\r\n" + "如来如去 如夢如幻 🌸🌺🌼 美しい世界 🌈🌠 🌕🌖🌗🌘\r\n" + + "العربية العربية العربية ✨🌙⭐ ☯️☪️✡️ 🕉️ 🔱\r\n" + "\r\n" + "🐉🦄🐲🐾 🐉🦄🐲 \uD83E\uDDFF\uD83D\uDC41\uFE0F\u200D\uD83D\uDDE8\uFE0F 👁️👁️ 🌀🌀🌀\r\n" + + "\uD83D\uDC68\u200D\uD83D\uDE80\uD83D\uDC69\u200D\uD83D\uDE80\uD83E\uDDD1\u200D\uD83D\uDE80 🚀🛸🚀🛸 🌌🌌🌌 💫💫💫\r\n" + "\r\n" + "𒀱𒂗𒆠 𒀭𒈹𒂊𒉡 𒌷𒆠𒂗𒄀 🏺🏺🏺\r\n" + "ᚠᛇᚻᚢᚦᚨᚱᚲ ⚔️🛡️ ⚔️🛡️\r\n" + "𑀓𑀺𑀢𑁆𑀢𑀺 𑀧𑀭𑀫𑀸𑀦 🌿🌿🌿\r\n" + + "\r\n" + "🌲🍃🌲🍃🌲 🍄🍄🍄 🌸🌸🌸\r\n" + "🌊🌊🌊 🌊🌊🌊 🐚🐚🐚 🐠🐟🐡\r\n" + "\r\n" + "💻📡📶 🧠🤖🧠 🤖🧠🤖 ⚙️⚙️⚙️\r\n" + "0101 ✨ 𐍈 0101 ✨ 𐍈\r\n" + "\r\n" + + "ქართული ქართული ქართული 🌄🌄\r\n" + "संस्कृतम् संस्कृतम् संस्कृतम् 🔱🔱\r\n" + "\r\n" + "🔥🔥🔥 ❄️❄️❄️ ⚡⚡⚡ 🌪️🌪️🌪️\r\n" + "❤️🧡💛💚💙💜🖤 🤍🤎 💖💗💓\r\n" + + "\r\n" + "🌐🌐🌐 🌐🌐🌐 🌀🌀🌀 🌀🌀🌀\r\n" + "💫✨🌟⭐✨💫 🌟✨⭐💫✨🌟\r\n" + "\r\n" + "𓂀𓂀𓂀 𓇼𓇼𓇼 𓆣𓆣𓆣\r\n" + "𐍈𐍈𐍈 ᚠᚠᚠ 𒀱𒀱𒀱" + + "𓂀𓆣𓇼𓁹𓃰 𐍈𐌰𐌽𐌳𐌴𐍂 🌌✨🌀💫 🌍🌎🌏 🧬⚛️🔮 ∑∏√∞ ≈≠≡ ⌘⌬⌭ ⟁\r\n" + "如来如去 如夢如幻 🌸🌺🌼 美しい世界 🌈🌠 🌕🌖🌗🌘\r\n" + + "العربية العربية العربية ✨🌙⭐ ☯️☪️✡️ 🕉️ 🔱\r\n" + "\r\n" + "🐉🦄🐲🐾 🐉🦄🐲 \uD83E\uDDFF\uD83D\uDC41\uFE0F\u200D\uD83D\uDDE8\uFE0F 👁️👁️ 🌀🌀🌀\r\n" + + "\uD83D\uDC68\u200D\uD83D\uDE80\uD83D\uDC69\u200D\uD83D\uDE80\uD83E\uDDD1\u200D\uD83D\uDE80 🚀🛸🚀🛸 🌌🌌🌌 💫💫💫\r\n" + "\r\n" + "𒀱𒂗𒆠 𒀭𒈹𒂊𒉡 𒌷𒆠𒂗𒄀 🏺🏺🏺\r\n" + "ᚠᛇᚻᚢᚦᚨᚱᚲ ⚔️🛡️ ⚔️🛡️\r\n" + "𑀓𑀺𑀢𑁆𑀢𑀺 𑀧𑀭𑀫𑀸𑀦 🌿🌿🌿\r\n" + + "\r\n" + "🌲🍃🌲🍃🌲 🍄🍄🍄 🌸🌸🌸\r\n" + "🌊🌊🌊 🌊🌊🌊 🐚🐚🐚 🐠🐟🐡\r\n" + "\r\n" + "💻📡📶 🧠🤖🧠 🤖🧠🤖 ⚙️⚙️⚙️\r\n" + "0101 ✨ 𐍈 0101 ✨ 𐍈\r\n" + "\r\n" + + "ქართული ქართული ქართული 🌄🌄\r\n" + "संस्कृतम् संस्कृतम् संस्कृतम् 🔱🔱\r\n" + "\r\n" + "🔥🔥🔥 ❄️❄️❄️ ⚡⚡⚡ 🌪️🌪️🌪️\r\n" + "❤️🧡💛💚💙💜🖤 🤍🤎 💖💗💓\r\n" + + "\r\n" + "🌐🌐🌐 🌐🌐🌐 🌀🌀🌀 🌀🌀🌀\r\n" + "💫✨🌟⭐✨💫 🌟✨⭐💫✨🌟\r\n" + "\r\n" + "𓂀𓂀𓂀 𓇼𓇼𓇼 𓆣𓆣𓆣\r\n" + "𐍈𐍈𐍈 ᚠᚠᚠ 𒀱𒀱𒀱"; + + @State(Scope.Thread) + public static class Data { + @Param({ "latin", "simple", "complex" }) + String name; + CharBuffer cbuf; + ByteBuffer bytes; + + @Setup + public void setup() { + switch (name) { + case "latin": + init(LATIN); + break; + case "simple": + init(SIMPLE); + break; + case "complex": + init(COMPLEX); + break; + default: + } + } + + private void init(String str) { + cbuf = CharBuffer.allocate(str.length()); + str.getChars(0, str.length(), cbuf.array(), 0); + bytes = ByteBuffer.wrap(str.getBytes(StandardCharsets.UTF_8)); + } + } @Benchmark - public String utf8Encoder() { + public ByteBuffer encoderUtf8(Data data) { ByteBuffer b = ByteBuffer.allocate(4096); - Utf8Encoder.encode(DATA, b); - return new Utf8Decoder().append(b.array(), 0, b.position()).done(); + Encoder e = Encoder.from(StandardCharsets.UTF_8); + CharBuffer cbuf = data.cbuf.duplicate(); + while (cbuf.hasRemaining()) { + e.encode(cbuf, b, false); + b.clear(); + } + return b.clear(); } @Benchmark - public String charset() { + public CharBuffer decoderUtf8(Data data) { + CharBuffer c = CharBuffer.allocate(4096); + Decoder d = Decoder.from(StandardCharsets.UTF_8); + ByteBuffer bbuf = data.bytes.duplicate(); + while (bbuf.hasRemaining()) { + d.decode(bbuf, c, false); + c.clear(); + } + return c.clear(); + } + + @Benchmark + public ByteBuffer encoderCharset(Data data) { ByteBuffer b = ByteBuffer.allocate(4096); CharsetEncoder e = StandardCharsets.UTF_8.newEncoder(); - e.encode(CharBuffer.wrap(DATA), b, true); + CharBuffer cbuf = data.cbuf.duplicate(); + while (cbuf.hasRemaining()) { + e.encode(cbuf, b, false); + b.clear(); + } + return b.clear(); + } - b.flip(); + @Benchmark + public CharBuffer decoderCharset(Data data) { + CharBuffer c = CharBuffer.allocate(4096); CharsetDecoder d = StandardCharsets.UTF_8.newDecoder(); - CharBuffer c = CharBuffer.allocate(b.limit()); - d.decode(b, c, true); - return c.flip().toString(); + ByteBuffer bbuf = data.bytes.duplicate(); + while (bbuf.hasRemaining()) { + d.decode(bbuf, c, false); + c.clear(); + } + return c.clear(); + } + + @Benchmark + public ByteBuffer slowEncoderUtf8(Data data) { + ByteBuffer b = ByteBuffer.allocate(4096); + Encoder e = Encoder.from(StandardCharsets.UTF_8); + CharBuffer cbuf = data.cbuf.asReadOnlyBuffer(); + while (cbuf.hasRemaining()) { + e.encode(cbuf, b, false); + b.clear(); + } + return b.clear(); + } + + @Benchmark + public CharBuffer slowDecoderUtf8(Data data) { + CharBuffer c = CharBuffer.allocate(4096); + Decoder d = Decoder.from(StandardCharsets.UTF_8); + ByteBuffer bbuf = data.bytes.asReadOnlyBuffer(); + while (bbuf.hasRemaining()) { + d.decode(bbuf, c, false); + c.clear(); + } + return c.clear(); } @Benchmark - public String string() { - byte[] bytes = DATA.getBytes(StandardCharsets.UTF_8); - return new String(bytes, StandardCharsets.UTF_8); + public ByteBuffer slowEncoderCharset(Data data) { + ByteBuffer b = ByteBuffer.allocate(4096); + CharsetEncoder e = StandardCharsets.UTF_8.newEncoder(); + CharBuffer cbuf = data.cbuf.asReadOnlyBuffer(); + while (cbuf.hasRemaining()) { + e.encode(cbuf, b, false); + b.clear(); + } + return b.clear(); + } + + @Benchmark + public CharBuffer slowDecoderCharset(Data data) { + CharBuffer c = CharBuffer.allocate(4096); + CharsetDecoder d = StandardCharsets.UTF_8.newDecoder(); + ByteBuffer bbuf = data.bytes.asReadOnlyBuffer(); + while (bbuf.hasRemaining()) { + d.decode(bbuf, c, false); + c.clear(); + } + return c.clear(); } } diff --git a/unknow-server-bench/src/main/java/unknow/server/bench/Main.java b/unknow-server-bench/src/main/java/unknow/server/bench/Main.java index 8029c2f5..31de0ea6 100644 --- a/unknow-server-bench/src/main/java/unknow/server/bench/Main.java +++ b/unknow-server-bench/src/main/java/unknow/server/bench/Main.java @@ -17,13 +17,15 @@ public class Main { public static void main(String[] args) throws Exception { - Options o = new OptionsBuilder().forks(1).measurementIterations(10).verbosity(VerboseMode.SILENT).warmupIterations(5).build(); - try (PrintStream w = args.length > 0 ? new PrintStream(Files.newOutputStream(Paths.get(args[0])), false, StandardCharsets.UTF_8) : System.out) { - for (Class c : Arrays.asList(EncoderDecoder.class, BenchJaxb.class, BenchDocument.class, BenchProtostuff.class)) { + Options o = new OptionsBuilder().forks(1).measurementIterations(10).verbosity(VerboseMode.NORMAL).warmupIterations(5).build(); + + try (PrintStream w = args.length > 0 ? new PrintStream(Files.newOutputStream(Paths.get(args[0])), false, StandardCharsets.UTF_8) : System.err) { + for (Class c : Arrays.asList(EncoderDecoder.class/*, BenchJaxb.class, BenchDocument.class, BenchProtostuff.class*/)) { + Collection result = new Runner(new OptionsBuilder().parent(o).include(c.getName()).build()).run(); + w.println(c.getSimpleName()); w.println("```"); - Collection result = new Runner(new OptionsBuilder().parent(o).include(c.getName()).build()).run(); ResultFormatFactory.getInstance(ResultFormatType.TEXT, w).writeOut(result); w.println("```"); } diff --git a/unknow-server-nio/src/main/java/unknow/server/nio/ConnectionStats.java b/unknow-server-nio/src/main/java/unknow/server/nio/ConnectionStats.java index f9d2550f..fa2a7b6d 100644 --- a/unknow-server-nio/src/main/java/unknow/server/nio/ConnectionStats.java +++ b/unknow-server-nio/src/main/java/unknow/server/nio/ConnectionStats.java @@ -1,5 +1,8 @@ package unknow.server.nio; +/** + * statistics of a nio connection + */ public class ConnectionStats { private final boolean hasPengingWrite; private final boolean closing; @@ -13,18 +16,30 @@ public ConnectionStats(boolean hasPengingWrite, boolean closing, long lastCheck, this.lastAction = lastAction; } + /** + * @return true if the connection has pending write + */ public boolean hasPengingWrite() { return hasPengingWrite; } + /** + * @return true if the connection is closing + */ public boolean isClosing() { return closing; } + /** + * @return nanotime of last connection check + */ public long lastCheck() { return lastCheck; } + /** + * @return nano time of last action on the connection + */ public long lastAction() { return lastAction; } diff --git a/unknow-server-nio/src/main/java/unknow/server/nio/NIOConnection.java b/unknow-server-nio/src/main/java/unknow/server/nio/NIOConnection.java index f5fe7902..a1b422c0 100644 --- a/unknow-server-nio/src/main/java/unknow/server/nio/NIOConnection.java +++ b/unknow-server-nio/src/main/java/unknow/server/nio/NIOConnection.java @@ -10,9 +10,10 @@ import java.nio.channels.SelectionKey; import java.nio.channels.SocketChannel; import java.util.Arrays; -import java.util.concurrent.BlockingQueue; +import java.util.Queue; +import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.Future; -import java.util.concurrent.LinkedBlockingDeque; +import java.util.concurrent.atomic.AtomicBoolean; import javax.net.ssl.SSLEngine; @@ -36,10 +37,13 @@ public final class NIOConnection extends NIOHandlerDelegate { protected final SelectionKey key; protected final SocketChannel channel; + private final AtomicBoolean writeScheduled; + private final WriteCheck writeCheck; + private InetSocketAddress local; private InetSocketAddress remote; - final BlockingQueue pending; + final Queue pending; final ByteBuffers writes; long lastCheck; @@ -59,8 +63,10 @@ public NIOConnection(NIOWorker worker, SelectionKey key, NIOConnectionHandler ha this.worker = worker; this.key = key; this.channel = (SocketChannel) key.channel(); + this.writeScheduled = new AtomicBoolean(false); + this.writeCheck = new WriteCheck(); this.out = new Out(this); - this.pending = new LinkedBlockingDeque<>(); + this.pending = new ConcurrentLinkedQueue<>(); this.writes = new ByteBuffers(16); } @@ -91,25 +97,14 @@ public void init(NIOConnection co, long now, SSLEngine sslEngine) throws IOExcep * add a buffers to the writing queue * * @param buf buffer to be written - * @throws InterruptedException in case of interruption - * @throws IOException + * @throws IOException in case of io error */ - public final void write(ByteBuffer buf) throws InterruptedException, IOException { + public final void write(ByteBuffer buf) throws IOException { if (!key.isValid()) throw new IOException("already closed"); - pending.put(buf); - if (pending.size() > 10) - flush(); - else - key.interestOpsOr(SelectionKey.OP_WRITE); - } - - @SuppressWarnings("resource") - public final void flush() { - if (!hasPendingWrites()) - return; - key.interestOpsOr(SelectionKey.OP_WRITE); - key.selector().wakeup(); + pending.offer(buf); + if (writeScheduled.compareAndSet(false, true)) + execute(writeCheck); } /** @@ -161,17 +156,23 @@ public boolean isClosed() { } protected final void beforeWrite(long now) throws IOException { - while (writes.len < 16 && !pending.isEmpty()) - handler.prepareWrite(pending.poll(), now, writes); - handler.beforeWrite(now, writes); + ByteBuffer b; + while ((b = pending.poll()) != null) + writes.accept(b); + handler.transformWrite(writes, now); } @Override public final void onWrite(long now) throws IOException { lastAction = now; writes.compact(); - if (!hasPendingWrites()) - key.interestOpsAnd(~SelectionKey.OP_WRITE); + if (!hasPendingWrites()) { + writeScheduled.set(false); + if (!hasPendingWrites()) + key.interestOpsAnd(~SelectionKey.OP_WRITE); + else if (writeScheduled.compareAndSet(false, true)) + key.interestOpsOr(SelectionKey.OP_WRITE); + } handler.onWrite(now); } @@ -203,18 +204,19 @@ public final void doneClosing() { @Override public String toString() { - return getClass() + "[local=" + getLocal() + " remote=" + getRemote() + "]"; + return getClass() + "[local=" + getLocal() + " remote=" + getRemote() + "] writes: " + hasPendingWrites(); } /** output stream for this connection */ public static final class Out extends OutputStream { + private static final int BUF_SIZE = 16 * 1024; private NIOConnection h; private ByteBuffer buf; private Out(NIOConnection h) { this.h = h; - this.buf = ByteBuffer.allocate(4096); + this.buf = ByteBuffer.allocate(BUF_SIZE); } @Override @@ -246,15 +248,10 @@ public synchronized void write(byte[] b, int off, int len) throws IOException { len -= r; off += r; writeBuffer(); - if (len < 4096) + if (len < BUF_SIZE) buf.put(b, off, len); else - try { - h.write(ByteBuffer.wrap(Arrays.copyOfRange(b, off, off + len))); - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - throw new IOException(e); - } + h.write(ByteBuffer.wrap(Arrays.copyOfRange(b, off, off + len))); } else if (len == r) { buf.put(b, off, len); writeBuffer(); @@ -272,12 +269,7 @@ public synchronized void write(ByteBuffer b) throws IOException { if (h == null) throw new IOException("already closed"); writeBuffer(); - try { - h.write(b); - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - throw new IOException(e); - } + h.write(b); } @Override @@ -302,19 +294,23 @@ public synchronized void flush() throws IOException { if (h == null) return; writeBuffer(); - h.flush(); } private void writeBuffer() throws IOException { if (h == null || buf.position() == 0) return; - try { - h.write(buf.flip()); - buf = ByteBuffer.allocate(4096); - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - throw new IOException(e); - } + h.write(buf.flip()); + buf = ByteBuffer.allocate(BUF_SIZE); + } + } + + private class WriteCheck implements WorkerTask { + @Override + public void run(long now) { + if (hasPendingWrites()) + key.interestOpsOr(SelectionKey.OP_WRITE); + else + writeScheduled.set(false); } } } diff --git a/unknow-server-nio/src/main/java/unknow/server/nio/NIOConnectionHandler.java b/unknow-server-nio/src/main/java/unknow/server/nio/NIOConnectionHandler.java index 679526c5..bb48b935 100644 --- a/unknow-server-nio/src/main/java/unknow/server/nio/NIOConnectionHandler.java +++ b/unknow-server-nio/src/main/java/unknow/server/nio/NIOConnectionHandler.java @@ -6,6 +6,11 @@ import javax.net.ssl.SSLEngine; +import unknow.server.util.io.ByteBuffers; + +/** + * handle nio connection event + */ @SuppressWarnings("unused") public interface NIOConnectionHandler { @@ -40,7 +45,7 @@ default void onHandshakeDone(SSLEngine sslEngine, long now) throws IOException { /** * called after some data has been read * - * @param b the read buffers + * @param b the read buffers (should read all or copy content) * @param now nanoTime * @throws IOException on io exception */ @@ -48,25 +53,13 @@ default void onRead(ByteBuffer b, long now) throws IOException { // ok } /** - * called before a buffer is written (allow to collect buffers) - * - * @param b buffer to be written - * @param now nanoTime - * @param c consumer of generated buffers - * @throws IOException on io exception - */ - default void prepareWrite(ByteBuffer b, long now, Consumer c) throws IOException { - c.accept(b); - } - - /** - * called before a buffer is written + * called before some buffers are written * + * @param buffers buffers to be written * @param now nanoTime - * @param c consumer of generated buffers * @throws IOException on io exception */ - default void beforeWrite(long now, Consumer c) throws IOException { // ok + default void transformWrite(ByteBuffers buffers, long now) throws IOException { // ok } /** diff --git a/unknow-server-nio/src/main/java/unknow/server/nio/NIOHandlerDelegate.java b/unknow-server-nio/src/main/java/unknow/server/nio/NIOHandlerDelegate.java index a3a43fb9..7331d0a9 100644 --- a/unknow-server-nio/src/main/java/unknow/server/nio/NIOHandlerDelegate.java +++ b/unknow-server-nio/src/main/java/unknow/server/nio/NIOHandlerDelegate.java @@ -2,10 +2,11 @@ import java.io.IOException; import java.nio.ByteBuffer; -import java.util.function.Consumer; import javax.net.ssl.SSLEngine; +import unknow.server.util.io.ByteBuffers; + public class NIOHandlerDelegate implements NIOConnectionHandler { protected final NIOConnectionHandler handler; @@ -34,13 +35,8 @@ public void onRead(ByteBuffer b, long now) throws IOException { } @Override - public void prepareWrite(ByteBuffer b, long now, Consumer c) throws IOException { - handler.prepareWrite(b, now, c); - } - - @Override - public void beforeWrite(long now, Consumer c) throws IOException { - handler.beforeWrite(now, c); + public void transformWrite(ByteBuffers buffers, long now) throws IOException { + handler.transformWrite(buffers, now); } @Override diff --git a/unknow-server-nio/src/main/java/unknow/server/nio/NIOLoop.java b/unknow-server-nio/src/main/java/unknow/server/nio/NIOLoop.java index ed1325c4..11205a94 100644 --- a/unknow-server-nio/src/main/java/unknow/server/nio/NIOLoop.java +++ b/unknow-server-nio/src/main/java/unknow/server/nio/NIOLoop.java @@ -12,6 +12,7 @@ import org.slf4j.LoggerFactory; /** + * basic selector loop * @author unknow */ public class NIOLoop implements Runnable { diff --git a/unknow-server-nio/src/main/java/unknow/server/nio/NIOSSLHandler.java b/unknow-server-nio/src/main/java/unknow/server/nio/NIOSSLHandler.java index 81e74440..672cad8e 100644 --- a/unknow-server-nio/src/main/java/unknow/server/nio/NIOSSLHandler.java +++ b/unknow-server-nio/src/main/java/unknow/server/nio/NIOSSLHandler.java @@ -4,14 +4,12 @@ import java.net.InetSocketAddress; import java.nio.ByteBuffer; import java.util.concurrent.atomic.AtomicInteger; -import java.util.function.Consumer; import javax.net.ssl.SSLContext; import javax.net.ssl.SSLEngine; import javax.net.ssl.SSLEngineResult; import javax.net.ssl.SSLEngineResult.HandshakeStatus; import javax.net.ssl.SSLEngineResult.Status; -import javax.net.ssl.SSLException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -25,20 +23,20 @@ public class NIOSSLHandler extends NIOHandlerDelegate { private static final ByteBuffer EMPTY = ByteBuffer.allocate(0); private final SSLContext sslContext; + private final ByteBuffers rawOut; private NIOConnection co; private SSLEngine sslEngine; private ByteBuffer rawIn; - private ByteBuffers rawOut; - private ByteBuffers app; + private ByteBuffer app; private boolean handshake; private int packetBufferSize; - private int applicationBufferSize; public NIOSSLHandler(SSLContext sslContext, NIOConnectionHandler handler) { super(handler); this.sslContext = sslContext; + this.rawOut = new ByteBuffers(16); } @Override @@ -52,13 +50,8 @@ public void init(NIOConnection co, long now, SSLEngine e) throws IOException { this.co = co; this.sslEngine = sslContext.createSSLEngine(remote.getHostString(), remote.getPort()); this.packetBufferSize = sslEngine.getSession().getPacketBufferSize(); - this.applicationBufferSize = sslEngine.getSession().getApplicationBufferSize(); this.rawIn = ByteBuffer.allocate(packetBufferSize); - this.rawOut = new ByteBuffers(16); - this.app = new ByteBuffers(16); - for (int i = 0; i < applicationBufferSize; i += 4096) - app.accept(ByteBuffer.allocate(4096)); - + this.app = ByteBuffer.allocate(sslEngine.getSession().getApplicationBufferSize()); this.handshake = true; handler.init(co, now, sslEngine); @@ -90,40 +83,34 @@ public void onRead(ByteBuffer b, long now) throws IOException { if (handshake) { rawIn.flip(); - SSLEngineResult r = sslEngine.unwrap(rawIn, app.buf, 0, app.len); - logger.trace("unwrap {}", r); + SSLEngineResult r = sslEngine.unwrap(rawIn, app); + logger.debug("unwrap {}", r); rawIn.compact(); if (checkHandshake(r.getHandshakeStatus(), now)) return; - app.drain(buf -> handler.onRead(buf, now)); + handler.onRead(app.flip(), now); + app.clear(); } rawIn.flip(); while (rawIn.hasRemaining()) { - SSLEngineResult r = sslEngine.unwrap(rawIn, app.buf, 0, app.len); - logger.trace("unwrap {}", r); + SSLEngineResult r = sslEngine.unwrap(rawIn, app); + logger.debug("unwrap {}", r); if (r.getStatus() == Status.BUFFER_UNDERFLOW || r.getStatus() == Status.CLOSED) break; - if (r.getStatus() == SSLEngineResult.Status.BUFFER_OVERFLOW) { - for (int i = app.remaining(); i < applicationBufferSize; i += 4096) - app.accept(ByteBuffer.allocate(4096)); - } else - app.drain(buf -> handler.onRead(buf, now)); + handler.onRead(app.flip(), now); + app.clear(); } rawIn.compact(); } @Override - public void prepareWrite(ByteBuffer b, long now, Consumer c) throws IOException { - handler.prepareWrite(b, now, rawOut); - } - - @Override - public void beforeWrite(long now, Consumer c) throws IOException { + public void transformWrite(ByteBuffers buffers, long now) throws IOException { + rawOut.accept(buffers); while (!rawOut.isEmpty()) { ByteBuffer out = ByteBuffer.allocate(packetBufferSize); SSLEngineResult r = sslEngine.wrap(rawOut.buf, 0, rawOut.len, out); - logger.trace("wrap {}", r); - c.accept(out.flip()); + logger.debug("wrap {}", r); + buffers.accept(out.flip()); if (r.getStatus() == Status.CLOSED) { AtomicInteger l = new AtomicInteger(0); rawOut.drain(b -> l.getAndAdd(b.remaining())); @@ -131,9 +118,16 @@ public void beforeWrite(long now, Consumer c) throws IOException { logger.warn("{} remaining data {}", co, l); break; } - rawOut.compact(); + while (r.getHandshakeStatus() == HandshakeStatus.NEED_WRAP) { + out = ByteBuffer.allocate(packetBufferSize); + r = sslEngine.wrap(rawOut.buf, 0, rawOut.len, out); + buffers.accept(out.flip()); + logger.debug("wrap {}", r); + } checkHandshake(r.getHandshakeStatus(), now); + rawOut.compact(); } + handler.transformWrite(buffers, now); } @Override @@ -143,17 +137,15 @@ public boolean hasPendingWrites() { @Override public boolean finishClosing(long now) { - if (!handler.finishClosing(now)) - return false; if (sslEngine.isOutboundDone()) return true; + if (!handler.finishClosing(now)) + return false; sslEngine.closeOutbound(); try { co.write(EMPTY); } catch (@SuppressWarnings("unused") IOException e) { // ok - } catch (@SuppressWarnings("unused") InterruptedException e) { - Thread.currentThread().interrupt(); } return false; } @@ -169,7 +161,7 @@ private boolean checkHandshake(HandshakeStatus hs, long now) throws IOException case NEED_UNWRAP: case NEED_UNWRAP_AGAIN: rawIn.flip(); - r = sslEngine.unwrap(rawIn, app.buf, 0, app.len); + r = sslEngine.unwrap(rawIn, app); logger.trace("unwrap {}", r); rawIn.compact(); if (r.getStatus() == Status.BUFFER_UNDERFLOW) @@ -177,13 +169,8 @@ private boolean checkHandshake(HandshakeStatus hs, long now) throws IOException hs = r.getHandshakeStatus(); break; case NEED_WRAP: - try { - co.write(EMPTY); - return true; - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - throw new SSLException("Handshake interrupted", e); - } + co.write(EMPTY); + return true; case FINISHED: onHandshakeDone(sslEngine, now); // fallthrough diff --git a/unknow-server-nio/src/main/java/unknow/server/nio/NIOServerListener.java b/unknow-server-nio/src/main/java/unknow/server/nio/NIOServerListener.java index 9b75b126..154d425b 100644 --- a/unknow-server-nio/src/main/java/unknow/server/nio/NIOServerListener.java +++ b/unknow-server-nio/src/main/java/unknow/server/nio/NIOServerListener.java @@ -11,6 +11,7 @@ import org.slf4j.LoggerFactory; /** + * nio server listener * @author unknow */ public interface NIOServerListener { diff --git a/unknow-server-nio/src/main/java/unknow/server/nio/NIOWorker.java b/unknow-server-nio/src/main/java/unknow/server/nio/NIOWorker.java index f75e0d17..11e5c9e0 100644 --- a/unknow-server-nio/src/main/java/unknow/server/nio/NIOWorker.java +++ b/unknow-server-nio/src/main/java/unknow/server/nio/NIOWorker.java @@ -33,7 +33,7 @@ public final class NIOWorker extends NIOLoop implements NIOWorkers { private static final Logger logger = LoggerFactory.getLogger(NIOWorker.class); - private static final int BUF_LEN = 16000; + private static final int BUF_LEN = 64 * 1024; /** executor for delegating task */ private final ExecutorService executor; @@ -141,7 +141,7 @@ private void finishClosing(long now) { Iterator it = closing.iterator(); while (it.hasNext()) { NIOConnection co = it.next(); - if (!co.key.isValid() || !co.hasPendingWrites() && co.finishClosing(now) || closingTimeout(co, now)) { + if (!co.key.isValid() || co.finishClosing(now) || closingTimeout(co, now)) { it.remove(); doneClose(co); } @@ -173,6 +173,7 @@ protected final void selected(long now, SelectionKey key) throws IOException { if (co.next != co) logger.error("failed to write {}", co, e); startClose(co, now); + key.cancel(); } finally { buf.clear(); } @@ -203,21 +204,18 @@ private void doRead(NIOConnection co, long now) throws IOException { toTail(co, now); if (l == 0) return; - buf.flip(); - ByteBuffer data = ByteBuffer.allocate(buf.remaining()); - data.put(buf); - data.flip(); - co.onRead(data, now); + co.onRead(buf.flip(), now); if (l < BUF_LEN) return; - buf.compact(); + buf.clear(); } } private void doWrite(NIOConnection co, long now) throws IOException { - co.beforeWrite(now); + if (co.writes.isEmpty()) + co.beforeWrite(now); long l = co.channel.write(co.writes.buf, 0, co.writes.len); - logger.trace("{} writen {}", co, l); + logger.trace("{} writen {} {}", co, l, co.key); if (l > 0) { co.onWrite(now); if (co.isClosed()) diff --git a/unknow-server-nio/src/main/java/unknow/server/nio/NIOWorkers.java b/unknow-server-nio/src/main/java/unknow/server/nio/NIOWorkers.java index efe41b1b..444ab3f7 100644 --- a/unknow-server-nio/src/main/java/unknow/server/nio/NIOWorkers.java +++ b/unknow-server-nio/src/main/java/unknow/server/nio/NIOWorkers.java @@ -11,6 +11,7 @@ import unknow.server.nio.NIOServer.ConnectionFactory; /** + * represent a worker groups * @author unknow */ public interface NIOWorkers { diff --git a/unknow-server-servlet/src/main/java/unknow/server/servlet/HttpConnection.java b/unknow-server-servlet/src/main/java/unknow/server/servlet/HttpConnection.java index cfbd481d..397947cf 100644 --- a/unknow-server-servlet/src/main/java/unknow/server/servlet/HttpConnection.java +++ b/unknow-server-servlet/src/main/java/unknow/server/servlet/HttpConnection.java @@ -6,7 +6,6 @@ import java.io.IOException; import java.net.InetSocketAddress; import java.nio.ByteBuffer; -import java.util.Arrays; import java.util.concurrent.Future; import java.util.concurrent.atomic.AtomicInteger; @@ -81,8 +80,8 @@ public void onHandshakeDone(SSLEngine sslEngine, long now) throws IOException { @Override public final void onRead(ByteBuffer b, long now) throws IOException { if (p == null) { - if (b.remaining() > Http2Processor.PRI.length - && Arrays.equals(b.array(), b.position(), b.position() + Http2Processor.PRI.length, Http2Processor.PRI, 0, Http2Processor.PRI.length)) + int mismatch = Http2Processor.PRI.mismatch(b); + if (mismatch == 24 || mismatch == -1) p = new Http2Processor(this); else p = new Http11Processor(this); @@ -120,7 +119,7 @@ public void startClose(long now) { @Override public boolean finishClosing(long now) { - return p == null || p.finishClosing(now); + return co.hasPendingWrites() || p == null || p.finishClosing(now); } @Override @@ -154,12 +153,7 @@ public final void execute(WorkerTask task) { } public void write(ByteBuffer buf) throws IOException { - try { - co.write(buf); - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - throw new IOException("Failed to write", e); - } + co.write(buf); } public ServletContextImpl getCtx() { diff --git a/unknow-server-servlet/src/main/java/unknow/server/servlet/http11/Http11Processor.java b/unknow-server-servlet/src/main/java/unknow/server/servlet/http11/Http11Processor.java index 4d5f0720..8a472241 100644 --- a/unknow-server-servlet/src/main/java/unknow/server/servlet/http11/Http11Processor.java +++ b/unknow-server-servlet/src/main/java/unknow/server/servlet/http11/Http11Processor.java @@ -59,17 +59,9 @@ private final void process(ByteBuffer b) throws IOException { decode(b); break; case CONTENT: - ByteBuffer slice = b.slice(); - if (b.remaining() < contentLength) { - contentLength -= b.remaining(); - dec.addContent(slice); - b.position(b.limit()); - } else { + if (readContent(b)) { state = START; - dec.addContent(slice.limit((int) contentLength)); dec.closeContent(); - b.position(b.position() + (int) contentLength); - contentLength = 0; } break; case CHUNKED_START: @@ -96,15 +88,8 @@ private final void process(ByteBuffer b) throws IOException { sb.append((char) c); break; case CHUNKED_DATA: - if (b.remaining() < contentLength) { - contentLength -= b.remaining(); - dec.addContent(b.slice()); - b.position(b.limit()); - } else { - // read CRLF + if (readContent(b)) { state = CHUNKED_END; - dec.addContent(b.slice().limit((int) contentLength)); - b.position(b.position() + (int) contentLength); contentLength = 0; } break; @@ -131,6 +116,21 @@ private final void process(ByteBuffer b) throws IOException { } } + private boolean readContent(ByteBuffer b) { + if (b.remaining() < contentLength) { + contentLength -= b.remaining(); + ByteBuffer data = ByteBuffer.allocate(b.remaining()); + dec.addContent(data.put(b).flip()); + return false; + } + ByteBuffer data = ByteBuffer.allocate((int) contentLength); + data.put(b.slice().limit((int) contentLength)); + dec.addContent(data.flip()); + b.position(b.position() + (int) contentLength); + contentLength = 0; + return true; + } + @Override public boolean canClose(long now, boolean stop) { return lock.isDone() && co.keepAliveReached(now); diff --git a/unknow-server-servlet/src/main/java/unknow/server/servlet/http11/Http11Worker.java b/unknow-server-servlet/src/main/java/unknow/server/servlet/http11/Http11Worker.java index e1678ee3..5a346b1c 100644 --- a/unknow-server-servlet/src/main/java/unknow/server/servlet/http11/Http11Worker.java +++ b/unknow-server-servlet/src/main/java/unknow/server/servlet/http11/Http11Worker.java @@ -4,6 +4,9 @@ import java.nio.charset.StandardCharsets; import java.util.Collection; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + import jakarta.servlet.http.Cookie; import unknow.server.nio.NIOConnection.Out; import unknow.server.servlet.HttpError; @@ -13,6 +16,7 @@ /** http/1.1 worker */ public final class Http11Worker extends HttpWorker { + private static final Logger logger = LoggerFactory.getLogger(Http11Worker.class); private static final byte[] CRLF = { '\r', '\n' }; private static final byte[] QUOTE = new byte[] { '\\', '"' }; @@ -102,6 +106,7 @@ else if (rawStream.isChunked()) { @SuppressWarnings("resource") @Override public final boolean doStart() throws IOException, InterruptedException { + logger.debug("{} doStart", co); if ("100-continue".equals(req.getHeader("expect"))) { Out out = co.getOut(); out.write(HttpError.CONTINUE.encoded); @@ -122,7 +127,9 @@ public final boolean doStart() throws IOException, InterruptedException { @Override protected void doDone() throws IOException { - if (!"keep-alive".equalsIgnoreCase(res.getHeader("connection"))) + String header = res.getHeader("connection"); + logger.debug("{} doDone {}", co, header); + if (!"keep-alive".equalsIgnoreCase(header)) co.getOut().close(); else co.flush(); diff --git a/unknow-server-servlet/src/main/java/unknow/server/servlet/http11/RequestDecoder.java b/unknow-server-servlet/src/main/java/unknow/server/servlet/http11/RequestDecoder.java index 452e8636..8254b3f1 100644 --- a/unknow-server-servlet/src/main/java/unknow/server/servlet/http11/RequestDecoder.java +++ b/unknow-server-servlet/src/main/java/unknow/server/servlet/http11/RequestDecoder.java @@ -16,14 +16,18 @@ private enum State { private final Http11Processor co; private final Utf8Decoder decoder; + private final byte[] buf; private ServletRequestImpl req; private State state; private int f; + private int o; + private int l; public RequestDecoder(Http11Processor co) { this.co = co; - decoder = new Utf8Decoder(new StringBuilder()); + decoder = new Utf8Decoder(); + buf = new byte[4096]; reset(); } @@ -41,24 +45,32 @@ public boolean closed() { public ServletRequestImpl append(ByteBuffer b) { while (b.hasRemaining()) { - tryDecode(b); - if (state == State.DONE) - return req; + int s = b.position(); + l = Math.min(b.remaining(), buf.length); + b.get(buf, 0, l); + o = 0; + while (o < l) { + tryDecode(); + if (state == State.DONE) { + b.position(s + o); + return req; + } + } } return null; } - private void tryDecode(ByteBuffer b) { + private void tryDecode() { String str; switch (state) { case METHOD: - if ((str = readUntil(b, SPACE)) != null) { + if ((str = readUntil(SPACE)) != null) { state = State.URI; req.setMethod(str); } return; case URI: - if ((str = readUntil(b, SPACE)) != null) { + if ((str = readUntil(SPACE)) != null) { state = State.PROTOCOL; int i = str.indexOf('?'); if (i > 0) { @@ -69,13 +81,13 @@ private void tryDecode(ByteBuffer b) { } return; case PROTOCOL: - if ((str = readUntil(b, CRLF)) != null) { + if ((str = readUntil(CRLF)) != null) { state = State.HEADER; req.setProtocol(str); } return; case HEADER: - if ((str = readUntil(b, CRLF)) != null) { + if ((str = readUntil(CRLF)) != null) { if (str.isEmpty()) { state = State.DONE; return; @@ -91,45 +103,38 @@ private void tryDecode(ByteBuffer b) { } } - private String readUntil(ByteBuffer b, byte c) { - byte[] a = b.array(); - int o = b.position() + b.arrayOffset(); - int l = b.limit() + b.arrayOffset(); - + private String readUntil(byte c) { int i = o; while (i < l) { - if (a[i] == c) { - String str = decoder.append(a, o, i).done(); - b.position(i + 1); + if (buf[i] == c) { + String str = decoder.append(buf, o, i).done(); + o = i + 1; return str; } i++; } - decoder.append(a, o, l); - b.position(l); + decoder.append(buf, o, l); + o = l; return null; } - private String readUntil(ByteBuffer b, byte[] c) { - byte[] a = b.array(); - int base = b.arrayOffset(); - int o = b.position() + base; - int l = b.limit() + base; - + private String readUntil(byte[] c) { int i = o; while (i < l) { - byte t = a[i++]; + byte t = buf[i++]; if (c[f++] != t) { decoder.append(c, 0, f - 1); f = 0; } else if (f == c.length) { + if (i > c.length) + decoder.append(buf, o, i - c.length); f = 0; - b.position(i - base); - return decoder.append(a, o, i - c.length).done(); + o = i; + return decoder.done(); } } - decoder.append(a, o, l - f); - b.position(l); + decoder.append(buf, o, l - f); + o = l; return null; } diff --git a/unknow-server-servlet/src/main/java/unknow/server/servlet/http2/Http2Processor.java b/unknow-server-servlet/src/main/java/unknow/server/servlet/http2/Http2Processor.java index 3d461728..fe5e7a02 100644 --- a/unknow-server-servlet/src/main/java/unknow/server/servlet/http2/Http2Processor.java +++ b/unknow-server-servlet/src/main/java/unknow/server/servlet/http2/Http2Processor.java @@ -34,7 +34,7 @@ public class Http2Processor implements NIOConnectionHandler, Http2FlowControl { static final Logger logger = LoggerFactory.getLogger(Http2Processor.class); - public static final byte[] PRI = "PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n".getBytes(StandardCharsets.US_ASCII); + public static final ByteBuffer PRI = ByteBuffer.wrap("PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n".getBytes(StandardCharsets.US_ASCII)); public static final int NO_ERROR = 0; public static final int PROTOCOL_ERROR = 1; diff --git a/unknow-server-servlet/src/main/java/unknow/server/servlet/impl/AbstractServletOutput.java b/unknow-server-servlet/src/main/java/unknow/server/servlet/impl/AbstractServletOutput.java index 8e5f8433..bbfb7f4d 100644 --- a/unknow-server-servlet/src/main/java/unknow/server/servlet/impl/AbstractServletOutput.java +++ b/unknow-server-servlet/src/main/java/unknow/server/servlet/impl/AbstractServletOutput.java @@ -29,7 +29,7 @@ protected AbstractServletOutput(ServletResponseImpl res, int position) { this.res = res; this.position = position; if (res != null) { - this.bufferSize = Math.max(res.getBufferSize(), 4096); + this.bufferSize = Math.max(res.getBufferSize(), 16 * 1024); this.buffer = ByteBuffer.allocate(bufferSize + position).position(position); } else { this.buffer = null; diff --git a/unknow-server-servlet/src/main/java/unknow/server/servlet/impl/ServletRequestImpl.java b/unknow-server-servlet/src/main/java/unknow/server/servlet/impl/ServletRequestImpl.java index cbd499fa..8f576485 100644 --- a/unknow-server-servlet/src/main/java/unknow/server/servlet/impl/ServletRequestImpl.java +++ b/unknow-server-servlet/src/main/java/unknow/server/servlet/impl/ServletRequestImpl.java @@ -5,7 +5,6 @@ import java.io.BufferedReader; import java.io.IOException; -import java.io.InputStreamReader; import java.io.Reader; import java.io.StringReader; import java.io.UnsupportedEncodingException; @@ -48,7 +47,10 @@ import unknow.server.servlet.HttpConnection; import unknow.server.servlet.impl.session.SessionFactory; import unknow.server.servlet.utils.PathUtils; +import unknow.server.servlet.utils.UrlDecoder; import unknow.server.util.data.ArrayMap; +import unknow.server.util.io.ByteBufferInputStream; +import unknow.server.util.io.ByteBufferReader; /** * @author unknow @@ -62,7 +64,7 @@ public class ServletRequestImpl implements HttpServletRequest { protected final HttpConnection co; private final DispatcherType type; - private final ServletInputStreamImpl input; + private final ByteBufferInputStream input; private String requestUri; private int pathInfoIndex; @@ -92,6 +94,7 @@ public class ServletRequestImpl implements HttpServletRequest { private List locales; private BufferedReader reader; + private ServletInputStreamImpl stream; /** * create new ServletRequestImpl @@ -102,13 +105,13 @@ public class ServletRequestImpl implements HttpServletRequest { public ServletRequestImpl(HttpConnection co, DispatcherType type) { this.co = co; this.type = type; - this.input = new ServletInputStreamImpl(); + this.input = new ByteBufferInputStream(); this.headers = new HashMap<>(); } public void append(ByteBuffer buf) { - input.append(buf); + input.addBuffer(buf); } public void close() { @@ -116,7 +119,7 @@ public void close() { } public boolean isClosed() { - return input.isFinished(); + return input.isClosed(); } public void setMethod(String method) { @@ -161,8 +164,10 @@ private void parseParam() { } try { - if ("POST".equals(getMethod()) && "application/x-www-form-urlencoded".equalsIgnoreCase(getContentType())) - parseContentParam(map); + if ("POST".equals(getMethod()) && "application/x-www-form-urlencoded".equalsIgnoreCase(getContentType())) { + new UrlDecoder(input).process(map); + contentLength = 0; + } } catch (IOException e) { logger.error("failed to parse params from content", e); } @@ -172,17 +177,6 @@ private void parseParam() { parameter.put(e.getKey(), e.getValue().toArray(s)); } - /** - * @param p - * @throws IOException - */ - private void parseContentParam(Map> p) throws IOException { - try (BufferedReader r = new BufferedReader(new InputStreamReader(input, getCharacterEncoding()))) { - PathUtils.pathQuery(r, p); - } - contentLength = 0; - } - /** * @param servletPath the servletPath to set */ @@ -193,7 +187,7 @@ public void setServletPath(String servletPath) { } public void clearInput() throws IOException { - while (!input.isFinished()) + while (!input.isClosed()) input.skip(Long.MAX_VALUE); } @@ -327,14 +321,18 @@ public long getContentLengthLong() { public ServletInputStream getInputStream() throws IOException { if (reader != null) throw new IllegalStateException("getReader() called"); - return input; + if (stream == null) + stream = new ServletInputStreamImpl(input); + return stream; } @Override public BufferedReader getReader() throws IOException { + if (stream != null) + throw new IllegalStateException("getInputStream() called"); if (reader != null) return reader; - return reader = new BufferedReader(new InputStreamReader(input, getCharacterEncoding())); + return reader = new BufferedReader(new ByteBufferReader(input, getCharacterEncoding())); } @Override diff --git a/unknow-server-servlet/src/main/java/unknow/server/servlet/impl/ServletWriter.java b/unknow-server-servlet/src/main/java/unknow/server/servlet/impl/ServletWriter.java index 1dd83e37..60d0e92d 100644 --- a/unknow-server-servlet/src/main/java/unknow/server/servlet/impl/ServletWriter.java +++ b/unknow-server-servlet/src/main/java/unknow/server/servlet/impl/ServletWriter.java @@ -4,30 +4,28 @@ import java.io.Writer; import java.nio.CharBuffer; import java.nio.charset.Charset; -import java.nio.charset.CharsetEncoder; -import java.nio.charset.CoderResult; -import java.nio.charset.CodingErrorAction; + +import unknow.server.util.Encoder; public class ServletWriter extends Writer { private final AbstractServletOutput out; - private final CharsetEncoder enc; + private final Charset c; + private final Encoder enc; public ServletWriter(AbstractServletOutput out, Charset c) { this.out = out; - this.enc = c.newEncoder().onMalformedInput(CodingErrorAction.REPORT).onUnmappableCharacter(CodingErrorAction.REPORT); + this.c = c; + this.enc = Encoder.from(c); } public void write(CharBuffer cbuf) throws IOException { if (out.isClosed()) throw new IOException("closed"); - CoderResult r = enc.encode(cbuf, out.buffer, false); - if (r.isError()) - throw new IOException("Invalid caracter found"); - while (r.isOverflow()) { + + enc.encode(cbuf, out.buffer, false); + while (cbuf.hasRemaining()) { out.flush(); - r = enc.encode(cbuf, out.buffer, false); - if (r.isError()) - throw new IOException("Invalid caracter found"); + enc.encode(cbuf, out.buffer, false); } } @@ -44,7 +42,7 @@ public void write(int c) throws IOException { public void write(String str, int off, int len) throws IOException { if (out.isClosed()) throw new IOException("closed"); - write(CharBuffer.wrap(str, off, off + len)); + out.write(str.getBytes(c)); } @Override @@ -62,6 +60,8 @@ public void flush() throws IOException { @Override public void close() throws IOException { + while (enc.flush(out.buffer)) + out.flush(); out.close(); } } diff --git a/unknow-server-servlet/src/main/java/unknow/server/servlet/utils/UrlDecoder.java b/unknow-server-servlet/src/main/java/unknow/server/servlet/utils/UrlDecoder.java new file mode 100644 index 00000000..974f8c96 --- /dev/null +++ b/unknow-server-servlet/src/main/java/unknow/server/servlet/utils/UrlDecoder.java @@ -0,0 +1,150 @@ +package unknow.server.servlet.utils; + +import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Map; + +public class UrlDecoder { + private static final byte EQ = '='; + private static final byte AM = '&'; + private static final byte SP = ' '; + private static final int[] HEX = new int[256]; + + static { + Arrays.fill(HEX, -1); + for (int i = '0'; i <= '9'; i++) + HEX[i] = i - '0'; + for (int i = 'A'; i <= 'F'; i++) + HEX[i] = 10 + i - 'A'; + for (int i = 'a'; i <= 'f'; i++) + HEX[i] = 10 + i - 'a'; + } + + private final InputStream in; + private final byte[] buf; + private int off; + private int len; + + private byte[] cbuf; + private int coff; + + private int pctState; // 0 normal, 1 = %, 2 = %X + private int pctHi; + + private String key; + + public UrlDecoder(InputStream in) { + this.in = in; + this.buf = new byte[4096]; + this.cbuf = new byte[4096]; + } + + public void process(Map> map) throws IOException { + while ((len = in.read(buf)) != -1) { + off = 0; + parse(map); + } + if (coff > 0) { + if (key == null) + key = string(); + add(map, string()); + } + } + + private void parse(Map> map) { + while (off < len) { + if (key == null) { + key = decode(); + if (key == null) + continue; + if (buf[off - 1] == AM) { + add(map, ""); + continue; + } + } + + String v = decode(); + if (v != null) + add(map, v); + + } + } + + private void add(Map> map, String value) { + List list = map.get(key); + if (list == null) + map.put(key, list = new ArrayList<>(1)); + list.add(value); + key = null; + } + + private String decode() { + int s = off; + while (off < len) { + byte c = buf[off++]; + if (pctState == 0 && c != '%' && c != '+' && c != EQ && c != AM) + continue; + + append(s); + if (pctState == 1) { + pctHi = HEX[c & 0xFF]; + if (pctHi < 0) + throw new IllegalArgumentException(); + pctState = 2; + continue; + } + if (pctState == 2) { + int lo = HEX[c & 0xFF]; + if (lo < 0) + throw new IllegalArgumentException(); + int val = (pctHi << 4) | lo; + append((byte) val); + pctState = 0; + continue; + } + if (c == EQ || c == AM) + return string(); + + if (c == '%') { + pctState = 1; + continue; + } + if (c == '+') + append(SP); + } + append(s); + return null; + + } + + private String string() { + if (coff == 0) + return ""; + String s = new String(cbuf, 0, coff, StandardCharsets.UTF_8); + coff = 0; + return s; + } + + private void capacity(int l) { + while (cbuf.length < l) + cbuf = Arrays.copyOf(cbuf, cbuf.length * 2); + } + + private void append(int s) { + int l = off - s; + if (l == 0) + return; + capacity(coff + l); + System.arraycopy(buf, s, cbuf, coff, l); + coff += l; + } + + private void append(byte c) { + capacity(coff + 1); + cbuf[coff++] = c; + } +} diff --git a/unknow-server-servlet/src/main/java/unknow/server/servlet/utils/Utf8Encoder.java b/unknow-server-servlet/src/main/java/unknow/server/servlet/utils/Utf8Encoder.java index f21a37d6..6cb4b97c 100644 --- a/unknow-server-servlet/src/main/java/unknow/server/servlet/utils/Utf8Encoder.java +++ b/unknow-server-servlet/src/main/java/unknow/server/servlet/utils/Utf8Encoder.java @@ -46,7 +46,8 @@ public byte next() { code = ((code << 10) + str.charAt(i++)) + (0x10000 - (0xD800 << 10) - 0xDC00); else code = 0xFFFD; - } + } else if (code >= 0xDC00 && code <= 0xDFFF) + code = 0xFFFD; count++; if (code <= 0x7F) @@ -56,7 +57,7 @@ else if (code <= 0x7FF) { return (byte) (0xC0 | (code >> 6)); } if (code <= 0xFFFF) { - tmp[r++] = (byte) (0x80 | (0x80 | (code & 0x3F))); + tmp[r++] = (byte) (0x80 | (code & 0x3F)); tmp[r++] = (byte) (0x80 | ((code >> 6) & 0x3F)); return (byte) (0xE0 | (code >> 12)); } diff --git a/unknow-server-servlet/src/test/java/unknow/server/servlet/Test.java b/unknow-server-servlet/src/test/java/unknow/server/servlet/Test.java new file mode 100644 index 00000000..50db0881 --- /dev/null +++ b/unknow-server-servlet/src/test/java/unknow/server/servlet/Test.java @@ -0,0 +1,16 @@ +package unknow.server.servlet; + +import java.nio.ByteBuffer; + +import unknow.server.servlet.http2.Http2Processor; + +public class Test { + public static void main(String[] arg) { + ByteBuffer b = ByteBuffer.allocate(1024); + b.put(Http2Processor.PRI.array()); + b.put(new byte[] { 0, 0, 0 }); + b.flip(); + System.out.println(Http2Processor.PRI.remaining()); + System.out.println(Http2Processor.PRI.mismatch(b)); + } +} diff --git a/unknow-server-util/src/main/java/unknow/server/util/Decoder.java b/unknow-server-util/src/main/java/unknow/server/util/Decoder.java new file mode 100644 index 00000000..1654cbd4 --- /dev/null +++ b/unknow-server-util/src/main/java/unknow/server/util/Decoder.java @@ -0,0 +1,82 @@ +package unknow.server.util; + +import java.nio.ByteBuffer; +import java.nio.CharBuffer; +import java.nio.charset.Charset; +import java.nio.charset.CharsetDecoder; +import java.nio.charset.CoderResult; +import java.nio.charset.StandardCharsets; + +public interface Decoder { + /** + * create a nex decoder for a charset + * @param charset the charset + * @return the decoder + */ + public static Decoder from(Charset charset) { + if (charset.equals(StandardCharsets.UTF_8)) + return new Utf8Decoder(); + return new DefaultDecoder(charset.newDecoder()); + } + + /** + * append one byte + * @param bbuf byte to append + * @param cbuf produced data + * @param endOfInput the data is done + */ + void decode(ByteBuffer bbuf, CharBuffer cbuf, boolean endOfInput); + + /** + * flush remaining char + * @param cbuf output + * @return true if we need to recall flush with more space + */ + boolean flush(CharBuffer cbuf); + + public class DefaultDecoder implements Decoder { + private final CharsetDecoder dec; + private final ByteBuffer b; + private boolean endCalled; + + public DefaultDecoder(CharsetDecoder dec) { + this.dec = dec; + this.b = ByteBuffer.allocate(4096); + this.endCalled = false; + } + + @Override + public void decode(ByteBuffer bbuf, CharBuffer cbuf, boolean endOfInput) { + int l = bbuf.limit(); + while (bbuf.position() < l) { + b.put(bbuf.limit(Math.min(l, bbuf.position() + b.remaining()))); + CoderResult r = dec.decode(b.flip(), cbuf, endOfInput); + b.compact(); + bbuf.limit(l); + if (endOfInput) + endCalled = true; + if (r.isOverflow()) + return; + if (r.isError()) + throw new IllegalArgumentException(r.toString()); + } + } + + @Override + public boolean flush(CharBuffer cbuf) { + if (b.position() > 0 || !endCalled) { + CoderResult r = dec.decode(b.flip(), cbuf, true); + b.compact(); + endCalled = true; + if (r.isOverflow()) + return true; + if (r.isError()) + throw new IllegalArgumentException(r.toString()); + } + CoderResult r = dec.flush(cbuf); + if (r.isError()) + throw new IllegalArgumentException(r.toString()); + return r.isOverflow(); + } + } +} diff --git a/unknow-server-util/src/main/java/unknow/server/util/Encoder.java b/unknow-server-util/src/main/java/unknow/server/util/Encoder.java new file mode 100644 index 00000000..b2164d71 --- /dev/null +++ b/unknow-server-util/src/main/java/unknow/server/util/Encoder.java @@ -0,0 +1,85 @@ +package unknow.server.util; + +import java.nio.ByteBuffer; +import java.nio.CharBuffer; +import java.nio.charset.Charset; +import java.nio.charset.CharsetEncoder; +import java.nio.charset.CoderResult; +import java.nio.charset.CodingErrorAction; +import java.nio.charset.StandardCharsets; + +public interface Encoder { + static final byte[] REPL = { (byte) 0xEF, (byte) 0xBF, (byte) 0xBD }; + + /** + * get encoder from a charset + * @param charset the charset + * @return the encoder + */ + public static Encoder from(Charset charset) { + if (charset.equals(StandardCharsets.UTF_8)) + return new Utf8Encoder(); + return new DefaultEncoder(charset.newEncoder().replaceWith(REPL).onMalformedInput(CodingErrorAction.REPLACE)); + } + + /** + * append one byte + * @param cbuf char to encode + * @param bbuf byte output + * @param endOfInput the data is done + */ + void encode(CharBuffer cbuf, ByteBuffer bbuf, boolean endOfInput); + + /** + * flush remaining bytes + * @param bbuf output + * @return true if we need to recall flush with more space + */ + boolean flush(ByteBuffer bbuf); + + public class DefaultEncoder implements Encoder { + private final CharsetEncoder enc; + private final CharBuffer c; + private boolean endCalled; + + public DefaultEncoder(CharsetEncoder dec) { + this.enc = dec; + this.c = CharBuffer.allocate(4096); + this.endCalled = false; + } + + @Override + public void encode(CharBuffer cbuf, ByteBuffer bbuf, boolean endOfInput) { + int l = cbuf.limit(); + while (cbuf.position() < l) { + c.put(cbuf.limit(Math.min(l, cbuf.position() + c.remaining()))); + CoderResult r = enc.encode(c.flip(), bbuf, endOfInput); + c.compact(); + cbuf.limit(l); + if (endOfInput) + endCalled = true; + if (r.isOverflow()) + return; + if (r.isError()) + throw new IllegalArgumentException(r.toString()); + } + } + + @Override + public boolean flush(ByteBuffer bbuf) { + if (c.position() > 0 || !endCalled) { + CoderResult r = enc.encode(c.flip(), bbuf, true); + c.compact(); + endCalled = true; + if (r.isOverflow()) + return true; + if (r.isError()) + throw new IllegalArgumentException(r.toString()); + } + CoderResult r = enc.flush(bbuf); + if (r.isError()) + throw new IllegalArgumentException(r.toString()); + return r.isOverflow(); + } + } +} diff --git a/unknow-server-util/src/main/java/unknow/server/util/Utf8Decoder.java b/unknow-server-util/src/main/java/unknow/server/util/Utf8Decoder.java new file mode 100644 index 00000000..1c48cdd1 --- /dev/null +++ b/unknow-server-util/src/main/java/unknow/server/util/Utf8Decoder.java @@ -0,0 +1,245 @@ +package unknow.server.util; + +import java.lang.invoke.MethodHandles; +import java.lang.invoke.VarHandle; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.nio.CharBuffer; +import java.nio.charset.CharsetDecoder; +import java.nio.charset.StandardCharsets; + +public class Utf8Decoder implements Decoder { + private static final VarHandle INT = MethodHandles.byteArrayViewVarHandle(int[].class, ByteOrder.LITTLE_ENDIAN); + private static final VarHandle SHORT = MethodHandles.byteArrayViewVarHandle(short[].class, ByteOrder.LITTLE_ENDIAN); + + private final CharsetDecoder ascii = StandardCharsets.US_ASCII.newDecoder(); + + /** code point in building */ + private int cp; + /** min code point allowed */ + private int minCp; + /** remaining octet to read */ + private int r; + + @Override + public void decode(ByteBuffer bbuf, CharBuffer cbuf, boolean endOfInput) { + if (bbuf.hasArray() && cbuf.hasArray()) + fastDecode(bbuf, cbuf); + else { + slowDecode(bbuf.order(ByteOrder.LITTLE_ENDIAN), cbuf); + } + if (endOfInput && !bbuf.hasRemaining() && r > 0) + throw new IllegalArgumentException("Invalid UTF-8 string, truncated continuation bytes"); + } + + @Override + public boolean flush(CharBuffer cbuf) { + return false; + } + + private void fastDecode(ByteBuffer bbuf, CharBuffer cbuf) { + + byte[] barr = bbuf.array(); + int blim = bbuf.limit() + bbuf.arrayOffset(); + char[] carr = cbuf.array(); + int clim = cbuf.limit() - 1 + cbuf.arrayOffset(); + if (r > 0) { + int bpos = bbuf.position() + bbuf.arrayOffset(); + int cpos = cbuf.position() + cbuf.arrayOffset(); + remainingArray(bbuf, bpos, cbuf, cpos, cp, r); + if (r > 0) + return; + } + ascii.decode(bbuf, cbuf, false); + int bpos = bbuf.position() + bbuf.arrayOffset(); + int cpos = cbuf.position() + cbuf.arrayOffset(); + int code; + while (bpos < blim && cpos < clim) { + int b = barr[bpos++]; + if (b >= 0) + carr[cpos++] = (char) b; + else if (b < -64) + error(bbuf, bpos, cbuf, cpos, "Invalid UTF-8 start byte: 0x" + Integer.toString(b & 0xFF, 16)); + else if (b < -32) { + code = b & 0x1F; + if (bpos == blim) { + this.cp = code; + r = 1; + minCp = 0x80; + updatePos(bbuf, bpos, cbuf, cpos); + return; + } + b = barr[bpos++]; + if ((b & 0xc0) != 0x80) + error(bbuf, bpos, cbuf, cpos, "Invalid UTF-8 continuation byte"); + code = (code << 6) | (b & 0x3F); + if (code < 0x80) + error(bbuf, bpos, cbuf, cpos, "Invalid UTF-8 overlong sequence"); + carr[cpos++] = (char) code; + } else if (b < -16) { + code = b & 0x0F; + if (bpos + 1 >= blim) { + minCp = 0x800; + remainingArray(bbuf, bpos, cbuf, cpos, code, 2); + return; + } + short s = (short) SHORT.get(barr, bpos); + if ((s & 0xc0c0) != 0x8080) + error(bbuf, bpos, cbuf, cpos, "Invalid UTF-8 continuation byte"); + + code = (code << 12) | ((s & 0x3F) << 6) | ((s >> 8) & 0x3F); + if (code < 0x800) + error(bbuf, bpos, cbuf, cpos, "Invalid UTF-8 overlong sequence"); + carr[cpos++] = (char) code; + bpos += 2; + } else if (b < -8) { + code = b & 0x07; + if (bpos + 3 >= blim) { + minCp = 0x10000; + remainingArray(bbuf, bpos, cbuf, cpos, code, 3); + return; + } + int i = (int) INT.get(barr, bpos); + if ((i & 0x00C0C0C0) != 0x00808080) + error(bbuf, bpos, cbuf, cpos, "Invalid UTF-8 continuation byte"); + code = (code << 18) | ((i & 0x3F) << 12) | (((i >> 8) & 0x3F) << 6) | ((i >> 16) & 0x3F); + if (code < 0x10000) + error(bbuf, bpos, cbuf, cpos, "Invalid UTF-8 overlong sequence"); + if (code > 0xFFFF) { // Surrogate pair + code -= 0x10000; + carr[cpos++] = (char) ((code >> 10) | 0xD800); + carr[cpos++] = (char) ((code & 0x3FF) | 0xDC00); + } else + carr[cpos++] = (char) code; + bpos += 3; + } else + error(bbuf, bpos, cbuf, cpos, "Invalid UTF-8 start byte: 0x" + Integer.toString(b, 16)); + } + updatePos(bbuf, bpos, cbuf, cpos); + } + + private void remainingArray(ByteBuffer bbuf, int bpos, CharBuffer cbuf, int cpos, int cp, int r) { + byte[] barr = bbuf.array(); + int blim = bbuf.limit() + bbuf.arrayOffset(); + char[] carr = cbuf.array(); + int clim = cbuf.limit() - 1 + cbuf.arrayOffset(); + + while (bpos < blim && cpos < clim) { + int b = barr[bpos++]; + if ((b & 0xc0) != 0x80) + error(bbuf, bpos, cbuf, cpos, "Invalid UTF-8 continuation byte"); + cp = (cp << 6) | (b & 0x3F); + if (--r == 0) { + if (cp < minCp) + error(bbuf, bpos, cbuf, cpos, "Invalid UTF-8 overlong sequence " + Integer.toString(cp, 16) + " < " + Integer.toString(minCp, 16)); + if (cp > 0xFFFF) { // Surrogate pair + cp -= 0x10000; + carr[cpos++] = (char) ((cp >> 10) | 0xD800); + carr[cpos++] = (char) ((cp & 0x3FF) | 0xDC00); + } else + carr[cpos++] = (char) cp; + this.r = r; + updatePos(bbuf, bpos, cbuf, cpos); + return; + } + } + this.cp = cp; + this.r = r; + updatePos(bbuf, bpos, cbuf, cpos); + } + + private static void error(ByteBuffer bbuf, int bpos, CharBuffer cbuf, int cpos, String msg) { + updatePos(bbuf, bpos, cbuf, cpos); + throw new IllegalArgumentException(msg); + } + + private static void updatePos(ByteBuffer bbuf, int bpos, CharBuffer cbuf, int cpos) { + bbuf.position(bpos - bbuf.arrayOffset()); + cbuf.position(cpos - cbuf.arrayOffset()); + } + + private void slowDecode(ByteBuffer bbuf, CharBuffer cbuf) { + if (r > 0 && slowRemaining(bbuf, cbuf, cp, r)) + return; + + int code; + while (bbuf.hasRemaining() && cbuf.remaining() > 1) { + int b = bbuf.get(); + if (b >= 0) + cbuf.put((char) b); + else if (b < -64) + throw new IllegalArgumentException("Invalid UTF-8 start byte: 0x" + Integer.toString(b & 0xFF, 16)); + else if (b < -32) { + code = b & 0x1F; + if (!bbuf.hasRemaining()) { + minCp = 0x80; + r = 1; + this.cp = code; + return; + } + b = bbuf.get(); + if ((b & 0xc0) != 0x80) + throw new IllegalArgumentException("Invalid UTF-8 continuation byte"); + code = (code << 6) | (b & 0x3F); + if (code < 0x80) + throw new IllegalArgumentException("Invalid UTF-8 overlong sequence"); + cbuf.put((char) code); + } else if (b < -16) { + code = b & 0x0F; + if (bbuf.remaining() < 2) { + minCp = 0x800; + slowRemaining(bbuf, cbuf, code, 2); + return; + } + short s = bbuf.getShort(); + if ((s & 0xc0c0) != 0x8080) + throw new IllegalArgumentException("Invalid UTF-8 continuation byte"); + + code = (code << 12) | ((s & 0x3F) << 6) | ((s >> 8) & 0x3F); + if (code < 0x800) + throw new IllegalArgumentException("Invalid UTF-8 overlong sequence"); + cbuf.put((char) code); + } else if (b < -8) { + code = b & 0x07; + if (bbuf.remaining() < 4) { + minCp = 0x10000; + slowRemaining(bbuf, cbuf, code, 3); + return; + } + int i = bbuf.getInt(bbuf.position()); + bbuf.position(bbuf.position() + 3); + if ((i & 0x00C0C0C0) != 0x00808080) + throw new IllegalArgumentException("Invalid UTF-8 continuation byte"); + code = (code << 18) | ((i & 0x3F) << 12) | (((i >> 8) & 0x3F) << 6) | ((i >> 16) & 0x3F); + if (code < 0x10000) + throw new IllegalArgumentException("Invalid UTF-8 overlong sequence"); + slowAppend(cbuf, code); + } else + throw new IllegalArgumentException("Invalid UTF-8 start byte: 0x" + Integer.toString(b, 16)); + } + } + + private boolean slowRemaining(ByteBuffer bbuf, CharBuffer cbuf, int cp, int r) { + while (bbuf.hasRemaining() && cbuf.remaining() > 1) { + int b = bbuf.get(); + if ((b & 0xc0) != 0x80) + throw new IllegalArgumentException("Invalid UTF-8 continuation byte"); + cp = (cp << 6) | (b & 0x3F); + if (--r == 0) + slowAppend(cbuf, cp); + } + this.cp = cp; + this.r = r; + return r > 0; + } + + private void slowAppend(CharBuffer cbuf, int cp) { + if (cp < minCp) + throw new IllegalArgumentException("Invalid UTF-8 overlong sequence " + Integer.toString(cp, 16) + " < " + Integer.toString(minCp, 16)); + if (cp > 0xFFFF) { // Surrogate pair + cp -= 0x10000; + cbuf.put((char) ((cp >> 10) + 0xD800)).put((char) ((cp & 0x3FF) + 0xDC00)); + } else + cbuf.put((char) cp); + } +} diff --git a/unknow-server-util/src/main/java/unknow/server/util/Utf8Encoder.java b/unknow-server-util/src/main/java/unknow/server/util/Utf8Encoder.java new file mode 100644 index 00000000..92c7dd47 --- /dev/null +++ b/unknow-server-util/src/main/java/unknow/server/util/Utf8Encoder.java @@ -0,0 +1,170 @@ +package unknow.server.util; + +import java.lang.invoke.MethodHandles; +import java.lang.invoke.VarHandle; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.nio.CharBuffer; +import java.nio.charset.CharsetEncoder; +import java.nio.charset.StandardCharsets; + +public class Utf8Encoder implements Encoder { + private static final VarHandle INT = MethodHandles.byteArrayViewVarHandle(int[].class, ByteOrder.LITTLE_ENDIAN); + + private static final int INT_REPL = 0x00BDBFEF; + + private final CharsetEncoder ascii = StandardCharsets.US_ASCII.newEncoder(); + + private int hi; + + @Override + public void encode(CharBuffer cbuf, ByteBuffer bbuf, boolean endOfInput) { + if (!cbuf.hasRemaining() || bbuf.remaining() < 4) + return; + if (cbuf.hasArray() && bbuf.hasArray()) + fastEncode(cbuf, bbuf, endOfInput); + else + slowEncode(cbuf, bbuf.order(ByteOrder.LITTLE_ENDIAN), endOfInput); + } + + @Override + public boolean flush(ByteBuffer bbuf) { + if (hi != 0) { + if (bbuf.remaining() < 3) + return true; + bbuf.put(REPL); + hi = 0; + } + return false; + } + + private void fastEncode(CharBuffer cbuf, ByteBuffer bbuf, boolean endOfInput) { + char[] carr = cbuf.array(); + int coff = cbuf.arrayOffset(); + int clim = cbuf.limit() + coff; + + byte[] barr = bbuf.array(); + int boff = bbuf.arrayOffset(); + int blim = bbuf.limit() - 3 + boff; + + if (hi != 0) { + int bpos = bbuf.position() + boff; + int cpos = cbuf.position() + coff; + int low = carr[cpos]; + if (low >= 0xDC00 && low <= 0xDFFF) { + cbuf.position(cpos - coff + 1); + int c = 0x10000 + ((hi & 0x7FF) << 10) + (low & 0x3FF); + c = 0x808080F0 | (c >> 18) | ((c >> 4) & 0x00003F00) | ((c << 10) & 0x003F0000) | (c << 24 & 0x3F000000); + INT.set(barr, bpos, c); + bbuf.position(bpos - boff + 4); + } else { + INT.set(barr, bpos, INT_REPL); + bbuf.position(bpos - boff + 3); + } + hi = 0; + } + + ascii.encode(cbuf, bbuf, false); + int bpos = bbuf.position() + boff; + int cpos = cbuf.position() + coff; + + while (cpos < clim && bpos < blim) { + char code = carr[cpos++]; + if (code < 0x80) { + barr[bpos++] = (byte) code; + } else if (code < 0x800) { + int c = 0x80C0 | (code >> 6) | ((code << 8) & 0x3F00); + INT.set(barr, bpos, c); + bpos += 2; + } else if (code < 0xD800) { + int c = 0x008080E0 | (code >> 12) | ((code << 2) & 0x3F00) | (code << 16 & 0x3F0000); + INT.set(barr, bpos, c); + bpos += 3; + } else if (code <= 0xDC00) { + // high surrogate + if (cpos < clim) { + int low = carr[cpos]; + if (low >= 0xDC00 && low <= 0xDFFF) { + cpos++; + int c = 0x10000 + ((code & 0x7FF) << 10) + (low & 0x3FF); + c = 0x808080F0 | (c >> 18) | ((c >> 4) & 0x00003F00) | ((c << 10) & 0x003F0000) | (c << 24 & 0x3F000000); + INT.set(barr, bpos, c); + bpos += 4; + } else { + INT.set(barr, bpos, INT_REPL); + bpos += 3; + } + } else { + if (endOfInput) { + INT.set(barr, bpos, INT_REPL); + bpos += 3; + } else + hi = code; + break; + } + } else if (code < 0xE000) { // lone low surrogate + INT.set(barr, bpos, INT_REPL); + bpos += 3; + } else { + int c = 0x008080E0 | (code >> 12) | ((code << 2) & 0x3F00) | (code << 16 & 0x3F0000); + INT.set(barr, bpos, c); + bpos += 3; + } + } + cbuf.position(cpos - coff); + bbuf.position(bpos - boff); + } + + private void slowEncode(CharBuffer cbuf, ByteBuffer bbuf, boolean endOfInput) { + if (hi != 0) { + int cpos = cbuf.position(); + int low = cbuf.get(cpos); + if (slowSurrogate(bbuf, hi, low)) + cbuf.position(cpos + 1); + hi = 0; + } + while (cbuf.hasRemaining() && bbuf.remaining() >= 4) { + int code = cbuf.get(); + if (code < 0x80) { + bbuf.put((byte) code); + } else if (code < 0x800) { + bbuf.putShort((short) (0x80C0 | (code >> 6) | ((code << 8) & 0x3F00))); + } else if (code < 0xD800) { + int p = bbuf.position(); + int c = 0x008080E0 | (code >> 12) | ((code << 2) & 0x3F00) | (code << 16 & 0x3F0000); + bbuf.putInt(p, c).position(p + 3); + } else if (code <= 0xDC00) { + // high surrogate + if (cbuf.hasRemaining()) { + int cpos = cbuf.position(); + int low = cbuf.get(cpos); + if (slowSurrogate(bbuf, code, low)) + cbuf.position(cpos + 1); + } else { + if (endOfInput) + bbuf.put(REPL); + else + hi = code; + break; + } + } else if (code < 0xE000) // lone low surrogate + bbuf.put(REPL); + else { + int p = bbuf.position(); + int c = 0x008080E0 | (code >> 12) | ((code << 2) & 0x3F00) | (code << 16 & 0x3F0000); + bbuf.putInt(p, c).position(p + 3); + } + } + } + + private boolean slowSurrogate(ByteBuffer bbuf, int high, int low) { + if (low < 0xDC00 || low > 0xDFFF) { + bbuf.put(REPL); + return false; + } + int c = 0x10000 + ((high & 0x7FF) << 10) + (low & 0x3FF); + c = 0x808080F0 | (c >> 18) | ((c >> 4) & 0x00003F00) | ((c << 10) & 0x003F0000) | (c << 24 & 0x3F000000); + bbuf.putInt(c); + return true; + } +} diff --git a/unknow-server-util/src/main/java/unknow/server/util/io/ByteBufferCache.java b/unknow-server-util/src/main/java/unknow/server/util/io/ByteBufferCache.java new file mode 100644 index 00000000..39942d06 --- /dev/null +++ b/unknow-server-util/src/main/java/unknow/server/util/io/ByteBufferCache.java @@ -0,0 +1,67 @@ +package unknow.server.util.io; + +import java.nio.ByteBuffer; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.concurrent.atomic.AtomicInteger; + +public class ByteBufferCache { + private static final Map CACHES = new HashMap<>(); + + private final int size; + private final int max; + private final ConcurrentLinkedQueue idle; + private final AtomicInteger count; + + private ByteBufferCache(int size, int max) { + this.size = size; + this.max = max; + this.idle = new ConcurrentLinkedQueue<>(); + this.count = new AtomicInteger(0); + } + + public static void createCache(int size, int max) { + CACHES.computeIfAbsent(size, k -> new ByteBufferCache(size, max)); + } + + public static ByteBufferCached get(int size) { + ByteBufferCache b = CACHES.get(size); + if (b != null) + return b.get(); + return new ByteBufferCached(null, ByteBuffer.allocate(size)); + } + + public ByteBufferCached get() { + ByteBuffer poll = idle.poll(); + if (poll == null) + poll = ByteBuffer.allocate(size); + return new ByteBufferCached(this, poll); + } + + private void free(ByteBuffer b) { + b.clear(); + if (count.getAndUpdate(x -> x == max ? x : x + 1) < max) + idle.offer(b); + } + + public static class ByteBufferCached { + private final ByteBufferCache cache; + private ByteBuffer buf; + + private ByteBufferCached(ByteBufferCache cache, ByteBuffer buf) { + this.cache = cache; + this.buf = buf; + } + + public ByteBuffer buffer() { + return buf; + } + + public void free() { + if (cache != null) + cache.free(buf); + buf = null; + } + } +} diff --git a/unknow-server-util/src/main/java/unknow/server/util/io/ByteBufferInputStream.java b/unknow-server-util/src/main/java/unknow/server/util/io/ByteBufferInputStream.java index cfffd405..fd6be997 100644 --- a/unknow-server-util/src/main/java/unknow/server/util/io/ByteBufferInputStream.java +++ b/unknow-server-util/src/main/java/unknow/server/util/io/ByteBufferInputStream.java @@ -30,13 +30,17 @@ public void drain(Collection list) { buffers.drainTo(list); } + public boolean isOef() throws IOException { + return buffer() == EOF; + } + /** * get next buffer to read (wait if no buffer available) * @return * @throws InterruptedException * @throws IOException */ - private final ByteBuffer buffer() throws IOException { + final ByteBuffer buffer() throws IOException { if (current == null || current != EOF && !current.hasRemaining()) { try { current = buffers.take(); diff --git a/unknow-server-util/src/main/java/unknow/server/util/io/ByteBufferReader.java b/unknow-server-util/src/main/java/unknow/server/util/io/ByteBufferReader.java new file mode 100644 index 00000000..9667389d --- /dev/null +++ b/unknow-server-util/src/main/java/unknow/server/util/io/ByteBufferReader.java @@ -0,0 +1,87 @@ +package unknow.server.util.io; + +import java.io.IOException; +import java.io.Reader; +import java.nio.CharBuffer; +import java.nio.charset.Charset; + +import unknow.server.util.Decoder; + +public class ByteBufferReader extends Reader { + private final ByteBufferInputStream in; + private final Decoder decoder; + private final CharBuffer buf; + + public ByteBufferReader(ByteBufferInputStream in, String charset) { + this(in, Charset.forName(charset)); + } + + public ByteBufferReader(ByteBufferInputStream in, Charset charset) { + this.in = in; + this.decoder = Decoder.from(charset); + buf = CharBuffer.allocate(8192).flip(); + } + + private void decode() throws IOException { + if (buf.hasRemaining() || in.isOef()) + return; + buf.compact(); + boolean end = in.hasRemaining() && in.isClosed(); + if (!end || in.buffer().hasRemaining()) + decoder.decode(in.buffer(), buf, end); + else + decoder.flush(buf); + buf.flip(); + } + + @Override + public int read() throws IOException { + decode(); + if (!buf.hasRemaining()) + return -1; + return buf.get(); + } + + @Override + public int read(char[] cbuf, int off, int len) throws IOException { + decode(); + if (!buf.hasRemaining()) + return -1; + len = Math.min(len, buf.remaining()); + buf.get(cbuf, off, len); + return len; + } + + @Override + public int read(CharBuffer target) throws IOException { + decode(); + if (!buf.hasRemaining()) + return -1; + int l = buf.remaining(); + int r = target.remaining(); + if (r > l) { + target.put(buf); + return l; + } + l = buf.limit(); + target.put(buf.limit(buf.position() + r)); + buf.limit(l); + return r; + } + + @Override + public long skip(long n) throws IOException { + decode(); + if (!buf.hasRemaining()) + return 0; + int l = (int) Math.min(n, buf.remaining()); + buf.position(buf.position() + l); + return n; + } + + @Override + public void close() throws IOException { + in.close(); + } + +} diff --git a/unknow-server-util/src/main/java/unknow/server/util/io/ByteBuffers.java b/unknow-server-util/src/main/java/unknow/server/util/io/ByteBuffers.java index c1924055..6ee966a4 100644 --- a/unknow-server-util/src/main/java/unknow/server/util/io/ByteBuffers.java +++ b/unknow-server-util/src/main/java/unknow/server/util/io/ByteBuffers.java @@ -15,13 +15,26 @@ public ByteBuffers(int l) { len = 0; } + private void ensureCapacity(int l) { + if (l > buf.length) + buf = Arrays.copyOf(buf, l); + } + @Override public void accept(ByteBuffer b) { - if (len == buf.length) - buf = Arrays.copyOf(buf, len + 1); + ensureCapacity(len + 1); buf[len++] = b; } + public void accept(ByteBuffers buffers) { + if (buffers.isEmpty()) + return; + ensureCapacity(len + buffers.len); + System.arraycopy(buffers.buf, 0, buf, len, buffers.len); + len += buffers.len; + buffers.clear(); + } + public boolean isEmpty() { return len == 0; } @@ -72,4 +85,31 @@ public void drain(ConsumerWithException c) } len = 0; } + + /** + * drain buffers + * @param exception thrown from consumer + * @param c consumer of buffer + * @throws E from Consumer + */ + public void drainNonEmpty(ConsumerWithException c) throws E { + if (len == 0) + return; + + int i = 0; + while (i < len) { + ByteBuffer b = buf[i]; + if (b.position() == 0 && b.limit() == b.capacity()) + break; + c.accept(b.flip()); + i++; + } + if (i == 0) + return; + int l = len - 1; + len -= i; + System.arraycopy(buf, i, buf, 0, len); + while (l >= len) + buf[l] = null; + } } diff --git a/unknow-server-util/src/test/java/unknow/server/util/DecoderTest.java b/unknow-server-util/src/test/java/unknow/server/util/DecoderTest.java new file mode 100644 index 00000000..afe08ab6 --- /dev/null +++ b/unknow-server-util/src/test/java/unknow/server/util/DecoderTest.java @@ -0,0 +1,133 @@ +package unknow.server.util; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import java.nio.ByteBuffer; +import java.nio.CharBuffer; +import java.nio.charset.StandardCharsets; +import java.util.stream.Stream; + +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; + +public class DecoderTest { + static Stream utf8Strings() { + return Stream.of("", "hello", "éàç", "こんにちは", "😀😃😄😁", "hello é 😀", "𐍈"); + } + + static Stream invalidUtf8() { + return Stream.of(new byte[] { (byte) 0x80 }, new byte[] { (byte) 0xC0 }, new byte[] { (byte) 0xE0, (byte) 0x80 }, new byte[] { (byte) 0xF0, (byte) 0x80, (byte) 0x80 }, + new byte[] { (byte) 0xC0, (byte) 0xAF }, new byte[] { (byte) 0xE0, (byte) 0x9F, (byte) 0x80 }, new byte[] { (byte) 0xF8 }, new byte[] { (byte) 0xFF }); + } + + protected Decoder decoder() { + return new Decoder.DefaultDecoder(StandardCharsets.UTF_8.newDecoder()); + } + + protected void decodeAll(Decoder decoder, ByteBuffer bbuf, CharBuffer cbuf) { + decoder.decode(bbuf, cbuf, true); + while (decoder.flush(cbuf)) + ; + } + + protected void decodeChunck(Decoder decoder, ByteBuffer input, CharBuffer cbuf) { + int l = input.limit(); + for (int i = 0; i < l; i++) + decoder.decode(input.position(i).limit(i + 1), cbuf, i == l - 1); + while (decoder.flush(cbuf)) + ; + } + + @ParameterizedTest + @MethodSource("utf8Strings") + void testFastPath(String input) { + Decoder decoder = decoder(); + ByteBuffer bbuf = ByteBuffer.wrap(input.getBytes(StandardCharsets.UTF_8)); + CharBuffer cbuf = CharBuffer.allocate(100); + decodeAll(decoder, bbuf, cbuf); + + assertEquals(input, cbuf.flip().toString()); + } + + @ParameterizedTest + @MethodSource("utf8Strings") + void testFastPathChuncked(String input) { + Decoder decoder = decoder(); + + ByteBuffer bytes = ByteBuffer.wrap(input.getBytes(StandardCharsets.UTF_8)); + CharBuffer cbuf = CharBuffer.allocate(100); + decodeChunck(decoder, bytes, cbuf); + assertEquals(input, cbuf.flip().toString()); + } + + @ParameterizedTest + @MethodSource("invalidUtf8") + void testFastPathError(byte[] input) { + Decoder decoder = decoder(); + + ByteBuffer bbuf = ByteBuffer.wrap(input); + CharBuffer cbuf = CharBuffer.allocate(100); + + assertThrows(IllegalArgumentException.class, () -> decodeAll(decoder, bbuf, cbuf)); + } + + @ParameterizedTest + @MethodSource("invalidUtf8") + void testFastPathErrorChuncked(byte[] bytes) { + Decoder decoder = decoder(); + + ByteBuffer bbuf = ByteBuffer.wrap(bytes); + CharBuffer cbuf = CharBuffer.allocate(100); + + assertThrows(IllegalArgumentException.class, () -> decodeChunck(decoder, bbuf, cbuf)); + } + + @ParameterizedTest + @MethodSource("utf8Strings") + void testSlowPath(String input) { + Decoder decoder = decoder(); + + ByteBuffer bbuf = ByteBuffer.wrap(input.getBytes(StandardCharsets.UTF_8)).asReadOnlyBuffer(); + CharBuffer cbuf = CharBuffer.allocate(100); + + decodeAll(decoder, bbuf, cbuf); + + assertEquals(input, cbuf.flip().toString()); + } + + @ParameterizedTest + @MethodSource("utf8Strings") + void testSlowPathChuncked(String input) { + Decoder decoder = decoder(); + + ByteBuffer bytes = ByteBuffer.wrap(input.getBytes(StandardCharsets.UTF_8)).asReadOnlyBuffer(); + CharBuffer cbuf = CharBuffer.allocate(100); + + decodeChunck(decoder, bytes, cbuf); + + assertEquals(input, cbuf.flip().toString()); + } + + @ParameterizedTest + @MethodSource("invalidUtf8") + void testSlowPathError(byte[] input) { + Decoder decoder = decoder(); + + ByteBuffer bbuf = ByteBuffer.wrap(input).asReadOnlyBuffer(); + CharBuffer cbuf = CharBuffer.allocate(100); + + assertThrows(IllegalArgumentException.class, () -> decodeAll(decoder, bbuf, cbuf)); + } + + @ParameterizedTest + @MethodSource("invalidUtf8") + void testSlowPathErrorChuncked(byte[] bytes) { + Decoder decoder = decoder(); + + ByteBuffer bbuf = ByteBuffer.wrap(bytes).asReadOnlyBuffer(); + CharBuffer cbuf = CharBuffer.allocate(100); + + assertThrows(IllegalArgumentException.class, () -> decodeChunck(decoder, bbuf, cbuf)); + } +} diff --git a/unknow-server-util/src/test/java/unknow/server/util/EncoderTest.java b/unknow-server-util/src/test/java/unknow/server/util/EncoderTest.java new file mode 100644 index 00000000..e6fc6105 --- /dev/null +++ b/unknow-server-util/src/test/java/unknow/server/util/EncoderTest.java @@ -0,0 +1,85 @@ +package unknow.server.util; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.nio.ByteBuffer; +import java.nio.CharBuffer; +import java.nio.charset.CodingErrorAction; +import java.nio.charset.StandardCharsets; +import java.util.stream.Stream; + +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +public class EncoderTest { + static Stream utf8Strings() { + return Stream.of(Arguments.of("", ""), Arguments.of("hello", "hello"), Arguments.of("éàç", "éàç"), Arguments.of("こんにちは", "こんにちは"), + Arguments.of("😀😃😄😁", "😀😃😄😁"), Arguments.of("hello é 😀", "hello é 😀"), Arguments.of("𐍈", "𐍈"), Arguments.of("\uD800", "\uFFFD"), + Arguments.of("\uDC00", "\uFFFD"), Arguments.of("\uD800A", "\uFFFDA"), Arguments.of("A\uDC00", "A\uFFFD")); + } + + protected Encoder encoder() { + return new Encoder.DefaultEncoder(StandardCharsets.UTF_8.newEncoder().replaceWith(Encoder.REPL).onMalformedInput(CodingErrorAction.REPLACE)); + } + + private void encodeAll(Encoder encoder, CharBuffer cbuf, ByteBuffer bbuf) { + encoder.encode(cbuf, bbuf, true); + while (encoder.flush(bbuf)) + ; + } + + private void encodeChunck(Encoder encoder, CharBuffer input, ByteBuffer bbuf) { + int l = input.limit(); + for (int i = 0; i < l; i++) + encoder.encode(input.position(i).limit(i + 1), bbuf, i == l - 1); + while (encoder.flush(bbuf)) + ; + } + + @ParameterizedTest + @MethodSource("utf8Strings") + void testFastPath(String input, String expected) { + Encoder encoder = encoder(); + CharBuffer cbuf = CharBuffer.wrap(input.toCharArray()); + ByteBuffer bbuf = ByteBuffer.allocate(100); + + encodeAll(encoder, cbuf, bbuf); + assertEquals(expected, new String(bbuf.array(), 0, bbuf.position(), StandardCharsets.UTF_8)); + } + + @ParameterizedTest + @MethodSource("utf8Strings") + void testFastPathChuncked(String input, String expected) { + Encoder encoder = encoder(); + CharBuffer cbuf = CharBuffer.wrap(input.toCharArray()); + ByteBuffer bbuf = ByteBuffer.allocate(100); + encodeChunck(encoder, cbuf, bbuf); + assertEquals(expected, new String(bbuf.array(), 0, bbuf.position(), StandardCharsets.UTF_8)); + } + + @ParameterizedTest + @MethodSource("utf8Strings") + void testSlowPath(String input, String expected) { + Encoder encoder = encoder(); + CharBuffer cbuf = CharBuffer.wrap(input); + ByteBuffer bbuf = ByteBuffer.allocate(100); + + encodeAll(encoder, cbuf, bbuf); + + assertEquals(expected, new String(bbuf.array(), 0, bbuf.position(), StandardCharsets.UTF_8)); + } + + @ParameterizedTest + @MethodSource("utf8Strings") + void testSlowPathChuncked(String input, String expected) { + Encoder encoder = encoder(); + CharBuffer cbuf = CharBuffer.wrap(input); + ByteBuffer bbuf = ByteBuffer.allocate(100); + + encodeChunck(encoder, cbuf, bbuf); + + assertEquals(expected, new String(bbuf.array(), 0, bbuf.position(), StandardCharsets.UTF_8)); + } + +} diff --git a/unknow-server-util/src/test/java/unknow/server/util/Utf8DecoderTest.java b/unknow-server-util/src/test/java/unknow/server/util/Utf8DecoderTest.java new file mode 100644 index 00000000..badff088 --- /dev/null +++ b/unknow-server-util/src/test/java/unknow/server/util/Utf8DecoderTest.java @@ -0,0 +1,8 @@ +package unknow.server.util; + +public class Utf8DecoderTest extends DecoderTest { + @Override + protected unknow.server.util.Decoder decoder() { + return new Utf8Decoder(); + } +} diff --git a/unknow-server-util/src/test/java/unknow/server/util/Utf8EncoderTest.java b/unknow-server-util/src/test/java/unknow/server/util/Utf8EncoderTest.java new file mode 100644 index 00000000..ddaaea91 --- /dev/null +++ b/unknow-server-util/src/test/java/unknow/server/util/Utf8EncoderTest.java @@ -0,0 +1,10 @@ +package unknow.server.util; + +public class Utf8EncoderTest extends EncoderTest { + + @Override + protected unknow.server.util.Encoder encoder() { + return new Utf8Encoder(); + } + +}