Skip to content

Commit 38fe781

Browse files
Update tutorial on loading local content (#206)
* Update tutorial on loading local content * Don't show the devtools * Fix text style issues
1 parent 6a83db2 commit 38fe781

File tree

5 files changed

+257
-146
lines changed

5 files changed

+257
-146
lines changed

tutorials/serve-from-directory/src/main/java/com/teamdev/jxbrowser/examples/interceptor/Application.java

Lines changed: 11 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -29,38 +29,37 @@
2929
import com.teamdev.jxbrowser.net.Scheme;
3030
import com.teamdev.jxbrowser.view.swing.BrowserView;
3131
import java.awt.BorderLayout;
32-
import java.nio.file.Path;
3332
import java.nio.file.Paths;
3433
import javax.swing.JFrame;
3534
import javax.swing.WindowConstants;
3635

3736
/**
38-
* This example demonstrates how to serve files from the folder for a certain domain.
37+
* This example demonstrates how to serve files from the folder for
38+
* a certain domain.
3939
*/
4040
public final class Application {
4141

4242
public static void main(String[] args) {
43-
Path contentRoot = Paths.get("content-root").toAbsolutePath();
44-
DomainToFolderInterceptor interceptor =
45-
DomainToFolderInterceptor.create("mydomain.com", contentRoot);
46-
EngineOptions options =
43+
var interceptor = new DomainToFolderInterceptor("mydomain.com",
44+
Paths.get("content-root"));
45+
var options =
4746
EngineOptions.newBuilder(HARDWARE_ACCELERATED)
48-
.addScheme(Scheme.HTTP, interceptor)
47+
.addScheme(Scheme.HTTPS, interceptor)
4948
.build();
50-
Engine engine = Engine.newInstance(options);
51-
Browser browser = engine.newBrowser();
49+
var engine = Engine.newInstance(options);
50+
var browser = engine.newBrowser();
5251

5352
invokeLater(() -> {
54-
BrowserView view = BrowserView.newInstance(browser);
53+
var view = BrowserView.newInstance(browser);
5554

56-
JFrame frame = new JFrame("Serve Files from Folder");
55+
var frame = new JFrame("Serve Files from Folder");
5756
frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
5857
frame.add(view, BorderLayout.CENTER);
5958
frame.setSize(1280, 720);
6059
frame.setLocationRelativeTo(null);
6160
frame.setVisible(true);
6261
});
6362

64-
browser.navigation().loadUrl("http://mydomain.com/index.html");
63+
browser.navigation().loadUrl("https://mydomain.com/index.html");
6564
}
6665
}
Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
/*
2+
* Copyright 2025, TeamDev. All rights reserved.
3+
*
4+
* Redistribution and use in source and/or binary forms, with or without
5+
* modification, must retain the above copyright notice and the following
6+
* disclaimer.
7+
*
8+
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
9+
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
10+
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
11+
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
12+
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
13+
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
14+
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
15+
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
16+
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
17+
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
18+
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
19+
*/
20+
21+
package com.teamdev.jxbrowser.examples.interceptor;
22+
23+
import static com.teamdev.jxbrowser.net.HttpStatus.INTERNAL_SERVER_ERROR;
24+
import static com.teamdev.jxbrowser.net.HttpStatus.NOT_FOUND;
25+
import static com.teamdev.jxbrowser.net.HttpStatus.OK;
26+
27+
import com.teamdev.jxbrowser.net.HttpHeader;
28+
import com.teamdev.jxbrowser.net.HttpStatus;
29+
import com.teamdev.jxbrowser.net.UrlRequestJob;
30+
import com.teamdev.jxbrowser.net.callback.InterceptUrlRequestCallback;
31+
import java.io.IOException;
32+
import java.io.InputStream;
33+
import java.net.URI;
34+
35+
/**
36+
* A base URL interceptor that serves content for a specific domain by
37+
* delegating the actual content lookup to subclasses.
38+
*
39+
* <p>This interceptor:
40+
* <ul>
41+
* <li>Intercepts only requests whose host matches the configured domain.</li>
42+
* <li>Delegates the content lookup to {@link #openContent(String)}.</li>
43+
* <li>Streams the returned content into the {@link UrlRequestJob}.</li>
44+
* </ul>
45+
*
46+
* <p>Subclasses need to provide the logic that locates the content (for example,
47+
* on disk or on the classpath) and returns it as an {@link InputStream}.
48+
* The MIME type is derived from the requested path by this base class.
49+
*/
50+
abstract class DomainContentInterceptor implements InterceptUrlRequestCallback {
51+
52+
private static final String CONTENT_TYPE = "Content-Type";
53+
private final String domain;
54+
55+
DomainContentInterceptor(String domain) {
56+
this.domain = domain;
57+
}
58+
59+
@Override
60+
public Response on(Params params) {
61+
var uri = URI.create(params.urlRequest().url());
62+
if (!uri.getHost().equals(domain)) {
63+
// Let Chromium process requests to other domains as usual.
64+
return Response.proceed();
65+
}
66+
var path = uri.getPath().substring(1);
67+
try (var content = openContent(path)) {
68+
if (content == null) {
69+
var job = createJob(params, NOT_FOUND);
70+
job.complete();
71+
return Response.intercept(job);
72+
}
73+
var mimeType = MimeTypes.mimeType(path);
74+
var contentType = HttpHeader.of(CONTENT_TYPE, mimeType);
75+
var job = createJob(params, OK, contentType);
76+
writeToJob(content, job);
77+
job.complete();
78+
return Response.intercept(job);
79+
} catch (IOException e) {
80+
// Return 500 response when the file read failed.
81+
var job = createJob(params, INTERNAL_SERVER_ERROR);
82+
job.complete();
83+
return Response.intercept(job);
84+
} catch (Exception e) {
85+
return Response.proceed();
86+
}
87+
}
88+
89+
/**
90+
* Locates the content for the given {@code uri}.
91+
*
92+
* <p>If the content cannot be found, this method should return
93+
* {@code null}. If the content cannot be read, it should throw an
94+
* {@link IOException}.
95+
*/
96+
protected abstract InputStream openContent(String path) throws IOException;
97+
98+
private UrlRequestJob createJob(Params params, HttpStatus status,
99+
HttpHeader... headers) {
100+
var options = UrlRequestJob.Options.newBuilder(status);
101+
for (var header : headers) {
102+
options.addHttpHeader(header);
103+
}
104+
return params.newUrlRequestJob(options.build());
105+
}
106+
107+
/**
108+
* Writes content of the input stream into the HTTP response.
109+
*/
110+
private void writeToJob(InputStream stream, UrlRequestJob job)
111+
throws IOException {
112+
var content = stream.readAllBytes();
113+
job.write(content);
114+
}
115+
}

tutorials/serve-from-directory/src/main/java/com/teamdev/jxbrowser/examples/interceptor/DomainToFolderInterceptor.java

Lines changed: 28 additions & 105 deletions
Original file line numberDiff line numberDiff line change
@@ -20,139 +20,62 @@
2020

2121
package com.teamdev.jxbrowser.examples.interceptor;
2222

23-
import static com.google.common.base.Preconditions.checkNotNull;
24-
import static com.teamdev.jxbrowser.examples.interceptor.MimeTypes.mimeType;
25-
import static com.teamdev.jxbrowser.internal.string.StringPreconditions.checkNotNullEmptyOrBlank;
26-
import static com.teamdev.jxbrowser.logging.Logger.error;
27-
import static com.teamdev.jxbrowser.net.HttpStatus.INTERNAL_SERVER_ERROR;
28-
import static com.teamdev.jxbrowser.net.HttpStatus.NOT_FOUND;
29-
import static com.teamdev.jxbrowser.net.HttpStatus.OK;
30-
import static java.util.Collections.emptyList;
31-
import static java.util.Collections.singletonList;
23+
import static java.nio.file.Files.exists;
24+
import static java.nio.file.Files.isDirectory;
3225

33-
import com.teamdev.jxbrowser.net.HttpHeader;
34-
import com.teamdev.jxbrowser.net.HttpStatus;
35-
import com.teamdev.jxbrowser.net.UrlRequestJob;
36-
import com.teamdev.jxbrowser.net.callback.InterceptUrlRequestCallback;
3726
import java.io.FileInputStream;
3827
import java.io.IOException;
39-
import java.net.URI;
40-
import java.nio.file.Files;
28+
import java.io.InputStream;
4129
import java.nio.file.Path;
42-
import java.nio.file.Paths;
43-
import java.util.Arrays;
44-
import java.util.List;
4530

4631
/**
47-
* An interceptor that treats every URL under the given domain as a path to the file on disk and
48-
* loads it.
32+
* An interceptor that treats every URL under the given domain as a path to a
33+
* file on disk and loads it.
4934
*
50-
* <p>The interceptor is configured with the domain name and the content directory. For every
51-
* request, it takes the path component of the URL and looks for it in the content directory. That
52-
* means a request to {@code example.com/docs/index.html} will load {@code docs/index.html} file
53-
* from the content directory. The mime type of the file is derived automatically.
35+
* <p>The interceptor is configured with the domain name and the content
36+
* directory. For every request, it takes the path component of the URL and
37+
* looks for it in the content directory. That means a request to
38+
* {@code example.com/docs/index.html} will load {@code docs/index.html} file
39+
* from the content directory. The MIME type of the file is derived
40+
* automatically.
5441
*
5542
* <p>This interceptor responds with the following status codes:
5643
*
5744
* <ul>
5845
* <li><b>200 OK</b> - the file was found and read properly.
5946
* In this case, the {@code Content-Type} header is sent.</li>
60-
* <li><b>404 Not Found</b> - the file could not be found due to an invalid path.</li>
47+
* <li><b>404 Not Found</b> - the file could not be found.</li>
6148
* <li><b>500 Internal Server Error</b> - couldn't read the file.</li>
6249
* </ul>
6350
*
64-
* <p>This interceptor considers only the path component of the URL request. It ignores request
65-
* parameters and headers.
66-
*
67-
* <p>Note: using this interceptor can reduce performance since it processes all incoming
68-
* traffic under the scheme.
51+
* <p>This interceptor considers only the path component of the URL request.
52+
* It ignores request parameters and headers.
6953
*/
70-
public final class DomainToFolderInterceptor implements InterceptUrlRequestCallback {
71-
72-
private static final String CONTENT_TYPE = "Content-Type";
54+
public final class DomainToFolderInterceptor extends DomainContentInterceptor {
7355

74-
private final String domain;
7556
private final Path contentRoot;
7657

77-
private DomainToFolderInterceptor(String domain, Path contentRoot) {
78-
this.domain = domain;
79-
this.contentRoot = contentRoot;
80-
}
81-
8258
/**
83-
* Creates a URL interceptor for the given domain to load files from the given directory.
59+
* Creates a URL interceptor for the given domain to load files from the
60+
* given directory.
8461
*
8562
* @param domain a domain name to intercept
8663
* @param contentRoot a path to the directory with files to load
8764
*/
88-
public static DomainToFolderInterceptor create(String domain, Path contentRoot) {
89-
checkNotNull(contentRoot);
90-
checkNotNullEmptyOrBlank(domain);
91-
return new DomainToFolderInterceptor(domain, contentRoot.toAbsolutePath());
65+
public DomainToFolderInterceptor(String domain, Path contentRoot) {
66+
super(domain);
67+
this.contentRoot = contentRoot.toAbsolutePath();
9268
}
9369

70+
/**
71+
* Resolves the requested path to a file and opens it.
72+
*/
9473
@Override
95-
public Response on(Params params) {
96-
URI uri = URI.create(params.urlRequest().url());
97-
if (shouldNotBeIntercepted(uri)) {
98-
return Response.proceed();
99-
}
100-
Path filePath = getPathOnDisk(uri);
101-
UrlRequestJob job;
102-
if (fileExists(filePath)) {
103-
HttpHeader contentType = getContentType(filePath);
104-
job = createJob(params, OK, singletonList(contentType));
105-
try {
106-
readFile(filePath, job);
107-
} catch (IOException e) {
108-
error("Failed to read file {0}", e, filePath);
109-
job = createJob(params, INTERNAL_SERVER_ERROR);
110-
}
111-
} else {
112-
job = createJob(params, NOT_FOUND);
113-
}
114-
job.complete();
115-
return Response.intercept(job);
116-
}
117-
118-
private boolean shouldNotBeIntercepted(URI uri) {
119-
return !uri.getHost().equals(domain);
120-
}
121-
122-
private Path getPathOnDisk(URI uri) {
123-
return Paths.get(contentRoot.toString(), uri.getPath());
124-
}
125-
126-
private boolean fileExists(Path filePath) {
127-
return Files.exists(filePath) && !Files.isDirectory(filePath);
128-
}
129-
130-
private HttpHeader getContentType(Path file) {
131-
return HttpHeader.of(CONTENT_TYPE, mimeType(file).value());
132-
}
133-
134-
private UrlRequestJob createJob(Params params,
135-
HttpStatus httpStatus,
136-
List<HttpHeader> httpHeaders) {
137-
UrlRequestJob.Options.Builder builder = UrlRequestJob.Options.newBuilder(httpStatus);
138-
httpHeaders.forEach(builder::addHttpHeader);
139-
return params.newUrlRequestJob(builder.build());
140-
}
141-
142-
private UrlRequestJob createJob(Params params, HttpStatus httpStatus) {
143-
return createJob(params, httpStatus, emptyList());
144-
}
145-
146-
private void readFile(Path filePath, UrlRequestJob job) throws IOException {
147-
try (FileInputStream stream = new FileInputStream(filePath.toFile())) {
148-
byte[] buffer = new byte[4096];
149-
int bytesRead;
150-
while ((bytesRead = stream.read(buffer)) > 0) {
151-
if (bytesRead != buffer.length) {
152-
buffer = Arrays.copyOf(buffer, bytesRead);
153-
}
154-
job.write(buffer);
155-
}
74+
protected InputStream openContent(String path) throws IOException {
75+
var filePath = contentRoot.resolve(path);
76+
if (exists(filePath) && !isDirectory(filePath)) {
77+
return new FileInputStream(filePath.toFile());
15678
}
79+
return null;
15780
}
15881
}

0 commit comments

Comments
 (0)