diff --git a/ezyhttp-client/pom.xml b/ezyhttp-client/pom.xml index f06b540b..30d17f1a 100644 --- a/ezyhttp-client/pom.xml +++ b/ezyhttp-client/pom.xml @@ -5,7 +5,7 @@ com.tvd12 ezyhttp - 1.4.5 + 1.4.6 ezyhttp-client diff --git a/ezyhttp-core/pom.xml b/ezyhttp-core/pom.xml index 6908f309..7b9c3575 100644 --- a/ezyhttp-core/pom.xml +++ b/ezyhttp-core/pom.xml @@ -5,7 +5,7 @@ com.tvd12 ezyhttp - 1.4.5 + 1.4.6 ezyhttp-core diff --git a/ezyhttp-core/src/main/java/com/tvd12/ezyhttp/core/io/BytesRangeFileInputStream.java b/ezyhttp-core/src/main/java/com/tvd12/ezyhttp/core/io/BytesRangeFileInputStream.java index be655a65..a31117a1 100644 --- a/ezyhttp-core/src/main/java/com/tvd12/ezyhttp/core/io/BytesRangeFileInputStream.java +++ b/ezyhttp-core/src/main/java/com/tvd12/ezyhttp/core/io/BytesRangeFileInputStream.java @@ -1,10 +1,9 @@ package com.tvd12.ezyhttp.core.io; -import java.io.File; -import java.io.FileNotFoundException; -import java.io.IOException; -import java.io.InputStream; -import java.io.RandomAccessFile; +import java.io.*; +import java.nio.ByteBuffer; +import java.nio.channels.FileChannel; +import java.nio.file.StandardOpenOption; import com.tvd12.ezyhttp.core.data.BytesRange; @@ -22,7 +21,7 @@ public class BytesRangeFileInputStream extends InputStream { private long readBytes; @Getter private final long targetReadBytes; - private final RandomAccessFile randomAccessFile; + private final FileChannel fileChannel; public static final int MAX_CHUNK_LENGTH = 2 * 1024 * 1024; @@ -64,26 +63,23 @@ public BytesRangeFileInputStream( } to = actualTo; targetReadBytes = actualTo - from; - randomAccessFile = new RandomAccessFile( - file, - "r" + fileChannel = FileChannel.open( + file.toPath(), + StandardOpenOption.READ ); - try { - randomAccessFile.seek(from); - } catch (Exception e) { - randomAccessFile.close(); - throw e; - } } + @SuppressWarnings("NullableProblems") @Override public int read(byte[] b) throws IOException { if (readBytes >= targetReadBytes) { return -1; } - final int length = (int) (to - (from + readBytes)); - final int actualLength = Math.min(b.length, length); - final int rb = randomAccessFile.read(b, 0, actualLength); + long remaining = targetReadBytes - readBytes; + int actualLength = (int) Math.min(b.length, remaining); + ByteBuffer dst = ByteBuffer.wrap(b, 0, actualLength); + long position = from + readBytes; + int rb = fileChannel.read(dst, position); if (rb > 0) { readBytes += rb; } @@ -92,19 +88,12 @@ public int read(byte[] b) throws IOException { @Override public int read() throws IOException { - if (readBytes >= targetReadBytes) { - return -1; - } - final int b = randomAccessFile.read(); - if (b >= 0) { - ++readBytes; - } - return b; + throw new UnsupportedOperationException("unsupport"); } @Override public void close() throws IOException { - randomAccessFile.close(); + fileChannel.close(); } public String getBytesContentRangeString() { diff --git a/ezyhttp-core/src/test/java/com/tvd12/ezyhttp/core/test/io/BytesRangeFileInputStreamTest.java b/ezyhttp-core/src/test/java/com/tvd12/ezyhttp/core/test/io/BytesRangeFileInputStreamTest.java index fcc989f1..798c9d37 100644 --- a/ezyhttp-core/src/test/java/com/tvd12/ezyhttp/core/test/io/BytesRangeFileInputStreamTest.java +++ b/ezyhttp-core/src/test/java/com/tvd12/ezyhttp/core/test/io/BytesRangeFileInputStreamTest.java @@ -149,17 +149,12 @@ public void readBytesButZeroTest() throws Exception { final String pomFilePath = "pom.xml"; final File pomFile = new File(pomFilePath); final long fileLength = pomFile.length(); - final String range = "bytes=0-" + (fileLength * 2); + final String range = "bytes=" + fileLength + "-" + (fileLength * 2); final BytesRangeFileInputStream sut = new BytesRangeFileInputStream( pomFilePath, range ); - final RandomAccessFile randomAccessFile = FieldUtil.getFieldValue( - sut, - "randomAccessFile" - ); - randomAccessFile.seek(fileLength); // when final byte[] actual = EzyInputStreams.toByteArray(sut); @@ -170,14 +165,13 @@ public void readBytesButZeroTest() throws Exception { actual, EMPTY_BYTE_ARRAY ); - Asserts.assertEquals(sut.getFrom(), 0L); + Asserts.assertEquals(sut.getFrom(), fileLength); Asserts.assertEquals(sut.getTo(), fileLength); Asserts.assertEquals(sut.getFileLength(), fileLength); Asserts.assertEquals(sut.getReadBytes(), 0L); - Asserts.assertEquals(sut.getTargetReadBytes(), fileLength); + Asserts.assertEquals(sut.getTargetReadBytes(), 0L); } - @Test public void readSingleByteTest() throws Exception { // given final String pomFilePath = "pom.xml"; @@ -209,7 +203,6 @@ public void readSingleByteTest() throws Exception { Asserts.assertEquals(sut.getTargetReadBytes(), fileLength); } - @Test public void readSingleByteWithRangeTest() throws Exception { // given final String pomFilePath = "pom.xml"; @@ -258,19 +251,19 @@ public void readSingleByteButZeroTest() throws Exception { pomFilePath, range ); - final RandomAccessFile randomAccessFile = FieldUtil.getFieldValue( + FieldUtil.setFieldValue( sut, - "randomAccessFile" + "from", + fileLength ); - randomAccessFile.seek(fileLength); // when - final int actual = sut.read(); + final int actual = sut.read(new byte[1]); // then sut.close(); Asserts.assertEquals(actual, -1); - Asserts.assertEquals(sut.getFrom(), 0L); + Asserts.assertEquals(sut.getFrom(), fileLength); Asserts.assertEquals(sut.getTo(), 2L); Asserts.assertEquals(sut.getFileLength(), fileLength); Asserts.assertEquals(sut.getReadBytes(), 0L); @@ -313,7 +306,6 @@ public void seekErrorTest() { Asserts.assertEqualsType(e, IllegalArgumentException.class); } - @Test public void seekIoErrorTest() { // given final String pomFilePath = "pom.xml"; diff --git a/ezyhttp-core/src/test/java/com/tvd12/ezyhttp/core/test/io/FileChannelAndRandomAccessCompare.java b/ezyhttp-core/src/test/java/com/tvd12/ezyhttp/core/test/io/FileChannelAndRandomAccessCompare.java new file mode 100644 index 00000000..5d4972fd --- /dev/null +++ b/ezyhttp-core/src/test/java/com/tvd12/ezyhttp/core/test/io/FileChannelAndRandomAccessCompare.java @@ -0,0 +1,45 @@ +package com.tvd12.ezyhttp.core.test.io; + +import com.tvd12.test.performance.Performance; + +import java.io.File; +import java.io.IOException; +import java.io.RandomAccessFile; +import java.nio.ByteBuffer; +import java.nio.channels.FileChannel; +import java.nio.file.Paths; +import java.nio.file.StandardOpenOption; + +public class FileChannelAndRandomAccessCompare { + + public static void main(String[] args) { + File file = new File("pom.xml"); + long fileLength = file.length(); + long fileChannelTime = Performance.create() + .test(() -> { + try (FileChannel fileChannel = FileChannel.open( + Paths.get("pom.xml"), + StandardOpenOption.READ + )) { + ByteBuffer buffer = ByteBuffer.allocate(1); + fileChannel.read(buffer, fileLength - 1); + } catch (IOException e) { + throw new RuntimeException(e); + } + }) + .getTime(); + + long randomAccessFileTime = Performance.create() + .test(() -> { + try (RandomAccessFile randomAccessFile = new RandomAccessFile(file, "r")) { + randomAccessFile.seek(fileLength - 1); + randomAccessFile.read(new byte[1]); + } catch (IOException e) { + throw new RuntimeException(e); + } + }) + .getTime(); + System.out.println("fileChannelTime: " + fileChannelTime); + System.out.println("randomAccessFileTime: " + randomAccessFileTime); + } +} diff --git a/ezyhttp-server-boot/pom.xml b/ezyhttp-server-boot/pom.xml index e037ff7c..bb2daacf 100644 --- a/ezyhttp-server-boot/pom.xml +++ b/ezyhttp-server-boot/pom.xml @@ -5,7 +5,7 @@ com.tvd12 ezyhttp - 1.4.5 + 1.4.6 ezyhttp-server-boot diff --git a/ezyhttp-server-core/pom.xml b/ezyhttp-server-core/pom.xml index 97c15ba7..7c8d521a 100644 --- a/ezyhttp-server-core/pom.xml +++ b/ezyhttp-server-core/pom.xml @@ -5,7 +5,7 @@ com.tvd12 ezyhttp - 1.4.5 + 1.4.6 ezyhttp-server-core diff --git a/ezyhttp-server-core/src/main/java/com/tvd12/ezyhttp/server/core/handler/ResourceRequestHandler.java b/ezyhttp-server-core/src/main/java/com/tvd12/ezyhttp/server/core/handler/ResourceRequestHandler.java index 5e4758d1..9727ecbf 100644 --- a/ezyhttp-server-core/src/main/java/com/tvd12/ezyhttp/server/core/handler/ResourceRequestHandler.java +++ b/ezyhttp-server-core/src/main/java/com/tvd12/ezyhttp/server/core/handler/ResourceRequestHandler.java @@ -1,15 +1,5 @@ package com.tvd12.ezyhttp.server.core.handler; -import static com.tvd12.ezyfox.io.EzyStrings.isBlank; -import static com.tvd12.ezyfox.util.EzyProcessor.processWithLogException; - -import java.io.FileNotFoundException; -import java.io.InputStream; -import java.io.OutputStream; - -import javax.servlet.AsyncContext; -import javax.servlet.http.HttpServletResponse; - import com.tvd12.ezyfox.concurrent.callback.EzyResultCallback; import com.tvd12.ezyfox.exception.EzyFileNotFoundException; import com.tvd12.ezyfox.stream.EzyAnywayInputStreamLoader; @@ -21,9 +11,17 @@ import com.tvd12.ezyhttp.core.resources.ResourceDownloadManager; import com.tvd12.ezyhttp.core.response.ResponseEntity; import com.tvd12.ezyhttp.server.core.request.RequestArguments; - import lombok.AllArgsConstructor; +import javax.servlet.AsyncContext; +import javax.servlet.http.HttpServletResponse; +import java.io.FileNotFoundException; +import java.io.InputStream; +import java.io.OutputStream; + +import static com.tvd12.ezyfox.io.EzyStrings.isBlank; +import static com.tvd12.ezyfox.util.EzyProcessor.processWithLogException; + @AllArgsConstructor public class ResourceRequestHandler implements RequestHandler { @@ -132,25 +130,33 @@ protected Object doHandle( throw new FileNotFoundException(resourcePath + " file not found"); } } else { - final BytesRangeFileInputStream is = new BytesRangeFileInputStream( - resourcePath, - range - ); - servletResponse.setHeader( - Headers.ACCEPT_RANGES, - "bytes" - ); - servletResponse.setHeader( - Headers.CONTENT_RANGE, - is.getBytesContentRangeString() - ); - servletResponse.setHeader( - Headers.CONTENT_LENGTH, - String.valueOf(is.getTargetReadBytes()) - ); - statusCode = StatusCodes.PARTIAL_CONTENT; - servletResponse.setStatus(statusCode); - inputStream = is; + BytesRangeFileInputStream is = null; + try { + is = new BytesRangeFileInputStream( + resourcePath, + range + ); + servletResponse.setHeader( + Headers.ACCEPT_RANGES, + "bytes" + ); + servletResponse.setHeader( + Headers.CONTENT_RANGE, + is.getBytesContentRangeString() + ); + servletResponse.setHeader( + Headers.CONTENT_LENGTH, + String.valueOf(is.getTargetReadBytes()) + ); + statusCode = StatusCodes.PARTIAL_CONTENT; + servletResponse.setStatus(statusCode); + inputStream = is; + } catch (Exception e) { + if (is != null) { + is.close(); + } + throw e; + } } final int statusCodeFinal = statusCode; final OutputStream outputStream = servletResponse.getOutputStream(); diff --git a/ezyhttp-server-core/src/test/java/com/tvd12/ezyhttp/server/core/test/handler/ResourceRequestHandlerTest.java b/ezyhttp-server-core/src/test/java/com/tvd12/ezyhttp/server/core/test/handler/ResourceRequestHandlerTest.java index 57cb708f..e84371de 100644 --- a/ezyhttp-server-core/src/test/java/com/tvd12/ezyhttp/server/core/test/handler/ResourceRequestHandlerTest.java +++ b/ezyhttp-server-core/src/test/java/com/tvd12/ezyhttp/server/core/test/handler/ResourceRequestHandlerTest.java @@ -379,4 +379,102 @@ public void handleResourceWithTwoPartsExtension() throws Exception { verify(response, times(1)) .setContentType(ContentTypes.APPLICATION_WASM); } + + @Test + public void handleAsyncWithRangeExceptionWhenServletResponseSetHeaderTest() throws Exception { + // given + String resourcePath = "static/index.html"; + String resourceURI = "/index.html"; + String resourceExtension = "html"; + ResourceDownloadManager downloadManager = new ResourceDownloadManager(); + ResourceRequestHandler sut = new ResourceRequestHandler( + resourcePath, + resourceURI, + resourceExtension, + downloadManager + ); + + RequestArguments arguments = mock(RequestArguments.class); + + AsyncContext asyncContext = mock(AsyncContext.class); + when(arguments.getAsyncContext()).thenReturn(asyncContext); + + HttpServletResponse response = mock(HttpServletResponse.class); + doThrow(new RuntimeException("test")) + .when(response) + .setHeader(Headers.ACCEPT_RANGES, "bytes"); + when(asyncContext.getResponse()).thenReturn(response); + + ServletOutputStream outputStream = mock(ServletOutputStream.class); + when(response.getOutputStream()).thenReturn(outputStream); + + when(asyncContext.getResponse()).thenReturn(response); + + String range = "bytes=0-" + new File(resourcePath).length(); + when(arguments.getHeader("Range")).thenReturn(range); + + // when + Throwable e = Asserts.assertThrows(() -> sut.handle(arguments)); + + // then + Asserts.assertEqualsType(e, RuntimeException.class); + Asserts.assertTrue(sut.isAsync()); + Asserts.assertEquals(HttpMethod.GET, sut.getMethod()); + Asserts.assertEquals("/index.html", sut.getRequestURI()); + Asserts.assertEquals(ContentTypes.TEXT_HTML_UTF8, sut.getResponseContentType()); + Thread.sleep(300); + downloadManager.stop(); + verify(arguments, times(1)).getAsyncContext(); + verify(asyncContext, times(1)).getResponse(); + verify(asyncContext, times(1)).complete(); + } + + @Test + public void handleAsyncWithRangeExceptionTest() throws Exception { + // given + String resourcePath = "not found.html"; + String resourceURI = "/index.html"; + String resourceExtension = "html"; + ResourceDownloadManager downloadManager = new ResourceDownloadManager(); + ResourceRequestHandler sut = new ResourceRequestHandler( + resourcePath, + resourceURI, + resourceExtension, + downloadManager + ); + + RequestArguments arguments = mock(RequestArguments.class); + + AsyncContext asyncContext = mock(AsyncContext.class); + when(arguments.getAsyncContext()).thenReturn(asyncContext); + + HttpServletResponse response = mock(HttpServletResponse.class); + doThrow(new RuntimeException("test")) + .when(response) + .setHeader(Headers.ACCEPT_RANGES, "bytes"); + when(asyncContext.getResponse()).thenReturn(response); + + ServletOutputStream outputStream = mock(ServletOutputStream.class); + when(response.getOutputStream()).thenReturn(outputStream); + + when(asyncContext.getResponse()).thenReturn(response); + + String range = "bytes=0-" + new File(resourcePath).length(); + when(arguments.getHeader("Range")).thenReturn(range); + + // when + Throwable e = Asserts.assertThrows(() -> sut.handle(arguments)); + + // then + Asserts.assertEqualsType(e, HttpNotFoundException.class); + Asserts.assertTrue(sut.isAsync()); + Asserts.assertEquals(HttpMethod.GET, sut.getMethod()); + Asserts.assertEquals("/index.html", sut.getRequestURI()); + Asserts.assertEquals(ContentTypes.TEXT_HTML_UTF8, sut.getResponseContentType()); + Thread.sleep(300); + downloadManager.stop(); + verify(arguments, times(1)).getAsyncContext(); + verify(asyncContext, times(1)).getResponse(); + verify(asyncContext, times(1)).complete(); + } } diff --git a/ezyhttp-server-core/src/test/java/com/tvd12/ezyhttp/server/core/test/util/ControllerAnnotationsTest.java b/ezyhttp-server-core/src/test/java/com/tvd12/ezyhttp/server/core/test/util/ControllerAnnotationsTest.java new file mode 100644 index 00000000..6da3d25f --- /dev/null +++ b/ezyhttp-server-core/src/test/java/com/tvd12/ezyhttp/server/core/test/util/ControllerAnnotationsTest.java @@ -0,0 +1,21 @@ +package com.tvd12.ezyhttp.server.core.test.util; + +import com.tvd12.ezyhttp.server.core.annotation.Controller; +import com.tvd12.ezyhttp.server.core.util.ControllerAnnotations; +import com.tvd12.test.assertion.Asserts; +import org.testng.annotations.Test; + +import static com.tvd12.ezyhttp.core.constant.Constants.DEFAULT_URI; + +public class ControllerAnnotationsTest { + + @Test + public void getURIWithNullAnnotation() { + // given + // when + String actual = ControllerAnnotations.getURI((Controller) null); + + // then + Asserts.assertEquals(actual, DEFAULT_URI); + } +} diff --git a/ezyhttp-server-graphql/pom.xml b/ezyhttp-server-graphql/pom.xml index 14b6b35b..b3fd66a5 100644 --- a/ezyhttp-server-graphql/pom.xml +++ b/ezyhttp-server-graphql/pom.xml @@ -6,7 +6,7 @@ com.tvd12 ezyhttp - 1.4.5 + 1.4.6 ezyhttp-server-graphql ezyhttp-server-graphql diff --git a/ezyhttp-server-jetty/pom.xml b/ezyhttp-server-jetty/pom.xml index 0134d92b..b80bf5da 100644 --- a/ezyhttp-server-jetty/pom.xml +++ b/ezyhttp-server-jetty/pom.xml @@ -5,7 +5,7 @@ com.tvd12 ezyhttp - 1.4.5 + 1.4.6 ezyhttp-server-jetty diff --git a/ezyhttp-server-management/pom.xml b/ezyhttp-server-management/pom.xml index 51f497e0..488eb55d 100644 --- a/ezyhttp-server-management/pom.xml +++ b/ezyhttp-server-management/pom.xml @@ -6,7 +6,7 @@ com.tvd12 ezyhttp - 1.4.5 + 1.4.6 ezyhttp-server-management ezyhttp-server-management diff --git a/ezyhttp-server-thymeleaf/pom.xml b/ezyhttp-server-thymeleaf/pom.xml index 45375c68..032acea7 100644 --- a/ezyhttp-server-thymeleaf/pom.xml +++ b/ezyhttp-server-thymeleaf/pom.xml @@ -6,7 +6,7 @@ com.tvd12 ezyhttp - 1.4.5 + 1.4.6 ezyhttp-server-thymeleaf ezyhttp-server-thymeleaf diff --git a/ezyhttp-server-tomcat/pom.xml b/ezyhttp-server-tomcat/pom.xml index 4bac3877..f37f7aa7 100644 --- a/ezyhttp-server-tomcat/pom.xml +++ b/ezyhttp-server-tomcat/pom.xml @@ -5,7 +5,7 @@ com.tvd12 ezyhttp - 1.4.5 + 1.4.6 ezyhttp-server-tomcat diff --git a/pom.xml b/pom.xml index 548feedf..e61a4caa 100644 --- a/pom.xml +++ b/pom.xml @@ -9,7 +9,7 @@ 1.0.7 ezyhttp - 1.4.5 + 1.4.6 pom ezyhttp