diff --git a/tutorials/serve-from-directory/src/main/java/com/teamdev/jxbrowser/examples/interceptor/Application.java b/tutorials/serve-from-directory/src/main/java/com/teamdev/jxbrowser/examples/interceptor/Application.java index dfa450fc..19e69ed8 100644 --- a/tutorials/serve-from-directory/src/main/java/com/teamdev/jxbrowser/examples/interceptor/Application.java +++ b/tutorials/serve-from-directory/src/main/java/com/teamdev/jxbrowser/examples/interceptor/Application.java @@ -29,31 +29,30 @@ import com.teamdev.jxbrowser.net.Scheme; import com.teamdev.jxbrowser.view.swing.BrowserView; import java.awt.BorderLayout; -import java.nio.file.Path; import java.nio.file.Paths; import javax.swing.JFrame; import javax.swing.WindowConstants; /** - * This example demonstrates how to serve files from the folder for a certain domain. + * This example demonstrates how to serve files from the folder for + * a certain domain. */ public final class Application { public static void main(String[] args) { - Path contentRoot = Paths.get("content-root").toAbsolutePath(); - DomainToFolderInterceptor interceptor = - DomainToFolderInterceptor.create("mydomain.com", contentRoot); - EngineOptions options = + var interceptor = new DomainToFolderInterceptor("mydomain.com", + Paths.get("content-root")); + var options = EngineOptions.newBuilder(HARDWARE_ACCELERATED) - .addScheme(Scheme.HTTP, interceptor) + .addScheme(Scheme.HTTPS, interceptor) .build(); - Engine engine = Engine.newInstance(options); - Browser browser = engine.newBrowser(); + var engine = Engine.newInstance(options); + var browser = engine.newBrowser(); invokeLater(() -> { - BrowserView view = BrowserView.newInstance(browser); + var view = BrowserView.newInstance(browser); - JFrame frame = new JFrame("Serve Files from Folder"); + var frame = new JFrame("Serve Files from Folder"); frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE); frame.add(view, BorderLayout.CENTER); frame.setSize(1280, 720); @@ -61,6 +60,6 @@ public static void main(String[] args) { frame.setVisible(true); }); - browser.navigation().loadUrl("http://mydomain.com/index.html"); + browser.navigation().loadUrl("https://mydomain.com/index.html"); } } diff --git a/tutorials/serve-from-directory/src/main/java/com/teamdev/jxbrowser/examples/interceptor/DomainContentInterceptor.java b/tutorials/serve-from-directory/src/main/java/com/teamdev/jxbrowser/examples/interceptor/DomainContentInterceptor.java new file mode 100644 index 00000000..41869f6d --- /dev/null +++ b/tutorials/serve-from-directory/src/main/java/com/teamdev/jxbrowser/examples/interceptor/DomainContentInterceptor.java @@ -0,0 +1,115 @@ +/* + * Copyright 2025, TeamDev. All rights reserved. + * + * Redistribution and use in source and/or binary forms, with or without + * modification, must retain the above copyright notice and the following + * disclaimer. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package com.teamdev.jxbrowser.examples.interceptor; + +import static com.teamdev.jxbrowser.net.HttpStatus.INTERNAL_SERVER_ERROR; +import static com.teamdev.jxbrowser.net.HttpStatus.NOT_FOUND; +import static com.teamdev.jxbrowser.net.HttpStatus.OK; + +import com.teamdev.jxbrowser.net.HttpHeader; +import com.teamdev.jxbrowser.net.HttpStatus; +import com.teamdev.jxbrowser.net.UrlRequestJob; +import com.teamdev.jxbrowser.net.callback.InterceptUrlRequestCallback; +import java.io.IOException; +import java.io.InputStream; +import java.net.URI; + +/** + * A base URL interceptor that serves content for a specific domain by + * delegating the actual content lookup to subclasses. + * + *
This interceptor: + *
Subclasses need to provide the logic that locates the content (for example, + * on disk or on the classpath) and returns it as an {@link InputStream}. + * The MIME type is derived from the requested path by this base class. + */ +abstract class DomainContentInterceptor implements InterceptUrlRequestCallback { + + private static final String CONTENT_TYPE = "Content-Type"; + private final String domain; + + DomainContentInterceptor(String domain) { + this.domain = domain; + } + + @Override + public Response on(Params params) { + var uri = URI.create(params.urlRequest().url()); + if (!uri.getHost().equals(domain)) { + // Let Chromium process requests to other domains as usual. + return Response.proceed(); + } + var path = uri.getPath().substring(1); + try (var content = openContent(path)) { + if (content == null) { + var job = createJob(params, NOT_FOUND); + job.complete(); + return Response.intercept(job); + } + var mimeType = MimeTypes.mimeType(path); + var contentType = HttpHeader.of(CONTENT_TYPE, mimeType); + var job = createJob(params, OK, contentType); + writeToJob(content, job); + job.complete(); + return Response.intercept(job); + } catch (IOException e) { + // Return 500 response when the file read failed. + var job = createJob(params, INTERNAL_SERVER_ERROR); + job.complete(); + return Response.intercept(job); + } catch (Exception e) { + return Response.proceed(); + } + } + + /** + * Locates the content for the given {@code uri}. + * + *
If the content cannot be found, this method should return + * {@code null}. If the content cannot be read, it should throw an + * {@link IOException}. + */ + protected abstract InputStream openContent(String path) throws IOException; + + private UrlRequestJob createJob(Params params, HttpStatus status, + HttpHeader... headers) { + var options = UrlRequestJob.Options.newBuilder(status); + for (var header : headers) { + options.addHttpHeader(header); + } + return params.newUrlRequestJob(options.build()); + } + + /** + * Writes content of the input stream into the HTTP response. + */ + private void writeToJob(InputStream stream, UrlRequestJob job) + throws IOException { + var content = stream.readAllBytes(); + job.write(content); + } +} diff --git a/tutorials/serve-from-directory/src/main/java/com/teamdev/jxbrowser/examples/interceptor/DomainToFolderInterceptor.java b/tutorials/serve-from-directory/src/main/java/com/teamdev/jxbrowser/examples/interceptor/DomainToFolderInterceptor.java index 8b189ada..5ed5419d 100644 --- a/tutorials/serve-from-directory/src/main/java/com/teamdev/jxbrowser/examples/interceptor/DomainToFolderInterceptor.java +++ b/tutorials/serve-from-directory/src/main/java/com/teamdev/jxbrowser/examples/interceptor/DomainToFolderInterceptor.java @@ -20,139 +20,62 @@ package com.teamdev.jxbrowser.examples.interceptor; -import static com.google.common.base.Preconditions.checkNotNull; -import static com.teamdev.jxbrowser.examples.interceptor.MimeTypes.mimeType; -import static com.teamdev.jxbrowser.internal.string.StringPreconditions.checkNotNullEmptyOrBlank; -import static com.teamdev.jxbrowser.logging.Logger.error; -import static com.teamdev.jxbrowser.net.HttpStatus.INTERNAL_SERVER_ERROR; -import static com.teamdev.jxbrowser.net.HttpStatus.NOT_FOUND; -import static com.teamdev.jxbrowser.net.HttpStatus.OK; -import static java.util.Collections.emptyList; -import static java.util.Collections.singletonList; +import static java.nio.file.Files.exists; +import static java.nio.file.Files.isDirectory; -import com.teamdev.jxbrowser.net.HttpHeader; -import com.teamdev.jxbrowser.net.HttpStatus; -import com.teamdev.jxbrowser.net.UrlRequestJob; -import com.teamdev.jxbrowser.net.callback.InterceptUrlRequestCallback; import java.io.FileInputStream; import java.io.IOException; -import java.net.URI; -import java.nio.file.Files; +import java.io.InputStream; import java.nio.file.Path; -import java.nio.file.Paths; -import java.util.Arrays; -import java.util.List; /** - * An interceptor that treats every URL under the given domain as a path to the file on disk and - * loads it. + * An interceptor that treats every URL under the given domain as a path to a + * file on disk and loads it. * - *
The interceptor is configured with the domain name and the content directory. For every - * request, it takes the path component of the URL and looks for it in the content directory. That - * means a request to {@code example.com/docs/index.html} will load {@code docs/index.html} file - * from the content directory. The mime type of the file is derived automatically. + *
The interceptor is configured with the domain name and the content + * directory. For every request, it takes the path component of the URL and + * looks for it in the content directory. That means a request to + * {@code example.com/docs/index.html} will load {@code docs/index.html} file + * from the content directory. The MIME type of the file is derived + * automatically. * *
This interceptor responds with the following status codes: * *
This interceptor considers only the path component of the URL request. It ignores request - * parameters and headers. - * - *
Note: using this interceptor can reduce performance since it processes all incoming - * traffic under the scheme. + *
This interceptor considers only the path component of the URL request.
+ * It ignores request parameters and headers.
*/
-public final class DomainToFolderInterceptor implements InterceptUrlRequestCallback {
-
- private static final String CONTENT_TYPE = "Content-Type";
+public final class DomainToFolderInterceptor extends DomainContentInterceptor {
- private final String domain;
private final Path contentRoot;
- private DomainToFolderInterceptor(String domain, Path contentRoot) {
- this.domain = domain;
- this.contentRoot = contentRoot;
- }
-
/**
- * Creates a URL interceptor for the given domain to load files from the given directory.
+ * Creates a URL interceptor for the given domain to load files from the
+ * given directory.
*
* @param domain a domain name to intercept
* @param contentRoot a path to the directory with files to load
*/
- public static DomainToFolderInterceptor create(String domain, Path contentRoot) {
- checkNotNull(contentRoot);
- checkNotNullEmptyOrBlank(domain);
- return new DomainToFolderInterceptor(domain, contentRoot.toAbsolutePath());
+ public DomainToFolderInterceptor(String domain, Path contentRoot) {
+ super(domain);
+ this.contentRoot = contentRoot.toAbsolutePath();
}
+ /**
+ * Resolves the requested path to a file and opens it.
+ */
@Override
- public Response on(Params params) {
- URI uri = URI.create(params.urlRequest().url());
- if (shouldNotBeIntercepted(uri)) {
- return Response.proceed();
- }
- Path filePath = getPathOnDisk(uri);
- UrlRequestJob job;
- if (fileExists(filePath)) {
- HttpHeader contentType = getContentType(filePath);
- job = createJob(params, OK, singletonList(contentType));
- try {
- readFile(filePath, job);
- } catch (IOException e) {
- error("Failed to read file {0}", e, filePath);
- job = createJob(params, INTERNAL_SERVER_ERROR);
- }
- } else {
- job = createJob(params, NOT_FOUND);
- }
- job.complete();
- return Response.intercept(job);
- }
-
- private boolean shouldNotBeIntercepted(URI uri) {
- return !uri.getHost().equals(domain);
- }
-
- private Path getPathOnDisk(URI uri) {
- return Paths.get(contentRoot.toString(), uri.getPath());
- }
-
- private boolean fileExists(Path filePath) {
- return Files.exists(filePath) && !Files.isDirectory(filePath);
- }
-
- private HttpHeader getContentType(Path file) {
- return HttpHeader.of(CONTENT_TYPE, mimeType(file).value());
- }
-
- private UrlRequestJob createJob(Params params,
- HttpStatus httpStatus,
- List The interceptor is configured with the domain name and the content
+ * root path. For every request, it takes the path component of the URL and
+ * looks for it in the classpath. That means a request to
+ * {@code example.com/docs/index.html} will load {@code docs/index.html} file
+ * from the resources. The MIME type of the file is derived
+ * automatically.
+ *
+ * This interceptor responds with the following status codes:
+ *
+ * This interceptor considers only the path component of the URL request.
+ * It ignores request parameters and headers.
+ */
+
+public final class DomainToResourceInterceptor extends
+ DomainContentInterceptor {
+
+ private final String resourceRoot;
+
+ /**
+ * Creates a URL interceptor for the given domain to load files from the
+ * given classpath root.
+ *
+ * @param domain a domain name to intercept
+ * @param resourceRoot a root path on the classpath to look up resources
+ * under
+ */
+ public DomainToResourceInterceptor(String domain, String resourceRoot) {
+ super(domain);
+ this.resourceRoot = resourceRoot;
+ }
+
+ protected InputStream openContent(String path) {
+ var resourcePath = toResourcePath(path);
+ return getClass().getClassLoader().getResourceAsStream(resourcePath);
+ }
+
+ private String toResourcePath(String uriPath) {
+ var path = uriPath.startsWith("/") ? uriPath.substring(1) : uriPath;
+ if (resourceRoot.isEmpty()) {
+ return path;
+ }
+ if (resourceRoot.endsWith("/")) {
+ return resourceRoot + path;
+ }
+ return resourceRoot + "/" + path;
+ }
+}
diff --git a/tutorials/serve-from-directory/src/main/java/com/teamdev/jxbrowser/examples/interceptor/MimeTypes.java b/tutorials/serve-from-directory/src/main/java/com/teamdev/jxbrowser/examples/interceptor/MimeTypes.java
index 7772db29..83079de3 100644
--- a/tutorials/serve-from-directory/src/main/java/com/teamdev/jxbrowser/examples/interceptor/MimeTypes.java
+++ b/tutorials/serve-from-directory/src/main/java/com/teamdev/jxbrowser/examples/interceptor/MimeTypes.java
@@ -21,26 +21,19 @@
package com.teamdev.jxbrowser.examples.interceptor;
import static com.teamdev.jxbrowser.logging.Logger.warn;
-import static java.lang.String.format;
+import static java.util.Locale.ROOT;
-import com.google.common.collect.ImmutableMap;
-import com.teamdev.jxbrowser.internal.Lazy;
-import com.teamdev.jxbrowser.net.MimeType;
import java.io.IOException;
-import java.io.InputStream;
-import java.net.URL;
-import java.nio.file.Path;
-import java.util.Locale;
-import java.util.Map;
import java.util.Properties;
/**
* A utility for working with MIME types.
*/
-public final class MimeTypes {
+final class MimeTypes {
- private static final MimeType OCTET_STREAM = MimeType.of("application/octet-stream");
- private static final Lazy
+ *
+ *
+ *