Skip to content

Commit 7807aee

Browse files
committed
Added MimeType, cookie response
1 parent 7b7ae87 commit 7807aee

File tree

10 files changed

+174
-19
lines changed

10 files changed

+174
-19
lines changed

Diff for: src/main/java/dev/latvian/apps/tinyserver/HTTPServer.java

+4-2
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import dev.latvian.apps.tinyserver.http.HTTPMethod;
77
import dev.latvian.apps.tinyserver.http.HTTPPathHandler;
88
import dev.latvian.apps.tinyserver.http.HTTPRequest;
9+
import dev.latvian.apps.tinyserver.http.Header;
910
import dev.latvian.apps.tinyserver.http.response.HTTPResponseBuilder;
1011
import dev.latvian.apps.tinyserver.http.response.HTTPStatus;
1112
import dev.latvian.apps.tinyserver.ws.WSEndpointHandler;
@@ -25,6 +26,7 @@
2526
import java.net.URLDecoder;
2627
import java.nio.charset.StandardCharsets;
2728
import java.time.Instant;
29+
import java.util.ArrayList;
2830
import java.util.Arrays;
2931
import java.util.EnumMap;
3032
import java.util.HashMap;
@@ -219,7 +221,7 @@ private void handleClient(Socket socket) {
219221
}
220222
}
221223

222-
var headers = new HashMap<String, String>();
224+
var headers = new ArrayList<Header>();
223225

224226
while (true) {
225227
var line = readLine(in);
@@ -231,7 +233,7 @@ private void handleClient(Socket socket) {
231233
var parts = line.split(":", 2);
232234

233235
if (parts.length == 2) {
234-
headers.put(parts[0].trim().toLowerCase(), parts[1].trim());
236+
headers.add(new Header(parts[0].trim(), parts[1].trim()));
235237
}
236238
}
237239

Diff for: src/main/java/dev/latvian/apps/tinyserver/content/ByteContent.java

+6
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package dev.latvian.apps.tinyserver.content;
22

33
import java.io.OutputStream;
4+
import java.net.http.HttpRequest;
45

56
public record ByteContent(byte[] bytes, String type) implements ResponseContent {
67
@Override
@@ -12,4 +13,9 @@ public long length() {
1213
public void write(OutputStream out) throws Exception {
1314
out.write(bytes);
1415
}
16+
17+
@Override
18+
public HttpRequest.BodyPublisher bodyPublisher() {
19+
return HttpRequest.BodyPublishers.ofByteArray(bytes);
20+
}
1521
}

Diff for: src/main/java/dev/latvian/apps/tinyserver/content/FileContent.java

+6
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import java.io.IOException;
44
import java.io.OutputStream;
5+
import java.net.http.HttpRequest;
56
import java.nio.file.Files;
67
import java.nio.file.Path;
78

@@ -32,4 +33,9 @@ public String type() {
3233
public void write(OutputStream out) throws Exception {
3334
Files.copy(file, out);
3435
}
36+
37+
@Override
38+
public HttpRequest.BodyPublisher bodyPublisher() throws IOException {
39+
return HttpRequest.BodyPublishers.ofByteArray(Files.readAllBytes(file));
40+
}
3541
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
package dev.latvian.apps.tinyserver.content;
2+
3+
public interface MimeType {
4+
String TEXT = "text/plain; charset=utf-8";
5+
String HTML = "text/html; charset=utf-8";
6+
String JSON = "application/json; charset=utf-8";
7+
String PNG = "image/png";
8+
String JPEG = "image/jpeg";
9+
String GIF = "image/gif";
10+
String FORM = "application/x-www-form-urlencoded";
11+
String XML = "application/xml; charset=utf-8";
12+
String XML_TEXT = "text/xml; charset=utf-8";
13+
String SVG = "image/svg+xml; charset=utf-8";
14+
String CSS = "text/css; charset=utf-8";
15+
String PDF = "application/pdf";
16+
String ZIP = "application/zip";
17+
String JAR = "application/java-archive";
18+
}

Diff for: src/main/java/dev/latvian/apps/tinyserver/content/ResponseContent.java

+6
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
package dev.latvian.apps.tinyserver.content;
22

3+
import java.io.IOException;
34
import java.io.OutputStream;
5+
import java.net.http.HttpRequest;
46

57
public interface ResponseContent {
68
default long length() {
@@ -12,4 +14,8 @@ default String type() {
1214
}
1315

1416
void write(OutputStream out) throws Exception;
17+
18+
default HttpRequest.BodyPublisher bodyPublisher() throws IOException {
19+
throw new IllegalStateException("Body publisher not supported");
20+
}
1521
}

Diff for: src/main/java/dev/latvian/apps/tinyserver/http/HTTPRequest.java

+12-5
Original file line numberDiff line numberDiff line change
@@ -9,17 +9,18 @@
99
import java.nio.charset.StandardCharsets;
1010
import java.util.Collections;
1111
import java.util.HashMap;
12+
import java.util.List;
1213
import java.util.Map;
1314

1415
public class HTTPRequest {
1516
private HTTPServer<?> server;
1617
private String[] path = new String[0];
1718
private Map<String, String> variables = Map.of();
1819
private Map<String, String> query = Map.of();
19-
private Map<String, String> headers = Map.of();
20+
private List<Header> headers = List.of();
2021
private InputStream bodyStream = null;
2122

22-
public void init(HTTPServer<?> server, String[] path, CompiledPath compiledPath, Map<String, String> headers, Map<String, String> query, InputStream bodyStream) {
23+
public void init(HTTPServer<?> server, String[] path, CompiledPath compiledPath, List<Header> headers, Map<String, String> query, InputStream bodyStream) {
2324
this.server = server;
2425
this.path = path;
2526

@@ -62,12 +63,18 @@ public Map<String, String> query() {
6263
return query;
6364
}
6465

65-
public Map<String, String> headers() {
66-
return Collections.unmodifiableMap(headers);
66+
public List<Header> headers() {
67+
return Collections.unmodifiableList(headers);
6768
}
6869

6970
public String header(String name) {
70-
return headers.getOrDefault(name.toLowerCase(), "");
71+
for (var header : headers) {
72+
if (header.key().equalsIgnoreCase(name)) {
73+
return header.value();
74+
}
75+
}
76+
77+
return "";
7178
}
7279

7380
public String[] path() {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
package dev.latvian.apps.tinyserver.http;
2+
3+
public record Header(String key, String value) {
4+
}

Diff for: src/main/java/dev/latvian/apps/tinyserver/http/response/HTTPResponse.java

+20-6
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,12 @@
22

33
import dev.latvian.apps.tinyserver.content.ByteContent;
44
import dev.latvian.apps.tinyserver.content.FileContent;
5+
import dev.latvian.apps.tinyserver.content.MimeType;
56
import dev.latvian.apps.tinyserver.content.ResponseContent;
67

78
import java.nio.charset.StandardCharsets;
89
import java.nio.file.Path;
10+
import java.util.function.Consumer;
911

1012
public interface HTTPResponse {
1113
static HTTPResponse ok() {
@@ -42,8 +44,22 @@ static HTTPResponse redirectPermanently(String location) {
4244

4345
void build(HTTPResponseBuilder payload) throws Exception;
4446

45-
default HTTPResponse header(String header, String value) {
46-
return new HTTPResponseWithHeader(this, header, value);
47+
default HTTPResponse header(String header, Object value) {
48+
return new HTTPResponseWithHeader(this, header, String.valueOf(value));
49+
}
50+
51+
default HTTPResponse cookie(String key, String value) {
52+
return new HTTPResponseWithCookie(this, key, value);
53+
}
54+
55+
default HTTPResponse cookie(String key, String value, int maxAge) {
56+
return new HTTPResponseWithCookie(this, key, value, new HTTPResponseWithCookie.Builder().maxAge(maxAge));
57+
}
58+
59+
default HTTPResponse cookie(String key, String value, Consumer<HTTPResponseWithCookie.Builder> properties) {
60+
var builder = new HTTPResponseWithCookie.Builder();
61+
properties.accept(builder);
62+
return new HTTPResponseWithCookie(this, key, value, builder);
4763
}
4864

4965
default HTTPResponse noCache() {
@@ -75,16 +91,14 @@ default HTTPResponse content(Path file) {
7591
}
7692

7793
default HTTPResponse text(String text) {
78-
return content(text.getBytes(StandardCharsets.UTF_8), "text/plain; charset=utf-8");
94+
return content(text.getBytes(StandardCharsets.UTF_8), MimeType.TEXT);
7995
}
8096

8197
default HTTPResponse text(Iterable<String> text) {
8298
return text(String.join("\n", text));
8399
}
84100

85101
default HTTPResponse json(String json) {
86-
return content(json.getBytes(StandardCharsets.UTF_8), "application/json; charset=utf-8");
102+
return content(json.getBytes(StandardCharsets.UTF_8), MimeType.JSON);
87103
}
88-
89-
90104
}

Diff for: src/main/java/dev/latvian/apps/tinyserver/http/response/HTTPResponseBuilder.java

+7-6
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package dev.latvian.apps.tinyserver.http.response;
22

33
import dev.latvian.apps.tinyserver.content.ResponseContent;
4+
import dev.latvian.apps.tinyserver.http.Header;
45
import dev.latvian.apps.tinyserver.ws.WSResponse;
56
import dev.latvian.apps.tinyserver.ws.WSSession;
67
import org.jetbrains.annotations.Nullable;
@@ -9,16 +10,16 @@
910
import java.nio.charset.StandardCharsets;
1011
import java.time.ZoneId;
1112
import java.time.format.DateTimeFormatter;
12-
import java.util.HashMap;
13+
import java.util.ArrayList;
14+
import java.util.List;
1315
import java.util.Locale;
14-
import java.util.Map;
1516

1617
public class HTTPResponseBuilder {
1718
public static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("EEE, dd MMM yyyy HH:mm:ss z", Locale.ENGLISH).withZone(ZoneId.of("GMT"));
1819
private static final byte[] CRLF = "\r\n".getBytes(StandardCharsets.UTF_8);
1920

2021
private HTTPStatus status = HTTPStatus.NO_CONTENT;
21-
private final Map<String, String> headers = new HashMap<>();
22+
private final List<Header> headers = new ArrayList<>();
2223
private ResponseContent body = null;
2324
private WSSession<?> wsSession = null;
2425

@@ -27,7 +28,7 @@ public void setStatus(HTTPStatus status) {
2728
}
2829

2930
public void setHeader(String header, Object value) {
30-
this.headers.put(header, String.valueOf(value));
31+
this.headers.add(new Header(header, String.valueOf(value)));
3132
}
3233

3334
public void setBody(ResponseContent body) {
@@ -38,8 +39,8 @@ public void write(OutputStream out, boolean writeBody) throws Exception {
3839
out.write(status.responseBytes);
3940
out.write(CRLF);
4041

41-
for (var entry : headers.entrySet()) {
42-
out.write((entry.getKey() + ": " + entry.getValue()).getBytes());
42+
for (var header : headers) {
43+
out.write((header.key() + ": " + header.value()).getBytes());
4344
out.write(CRLF);
4445
}
4546

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
package dev.latvian.apps.tinyserver.http.response;
2+
3+
public record HTTPResponseWithCookie(HTTPResponse original, String key, String value, Builder builder) implements HTTPResponse {
4+
public static class Builder {
5+
private static final Builder DEFAULT = new Builder();
6+
7+
private String domain = null;
8+
private int maxAge = 0;
9+
private boolean httpOnly = false;
10+
private boolean partitioned = false;
11+
private String path = null;
12+
private String sameSite = null;
13+
private boolean secure = false;
14+
15+
public Builder domain(String domain) {
16+
this.domain = domain;
17+
return this;
18+
}
19+
20+
public Builder maxAge(int maxAge) {
21+
this.maxAge = maxAge;
22+
return this;
23+
}
24+
25+
public Builder httpOnly() {
26+
this.httpOnly = true;
27+
return this;
28+
}
29+
30+
public Builder partitioned(boolean partitioned) {
31+
this.partitioned = partitioned;
32+
return this;
33+
}
34+
35+
public Builder path(String path) {
36+
this.path = path;
37+
return this;
38+
}
39+
40+
public Builder sameSite(String sameSite) {
41+
this.sameSite = sameSite;
42+
return this;
43+
}
44+
45+
public Builder secure() {
46+
this.secure = true;
47+
return this;
48+
}
49+
}
50+
51+
public HTTPResponseWithCookie(HTTPResponse original, String key, String value) {
52+
this(original, key, value, Builder.DEFAULT);
53+
}
54+
55+
@Override
56+
public void build(HTTPResponseBuilder payload) throws Exception {
57+
var sb = new StringBuilder();
58+
sb.append(key).append("=").append(value);
59+
60+
if (builder.domain != null) {
61+
sb.append("; Domain=").append(builder.domain);
62+
}
63+
64+
if (builder.maxAge >= 0) {
65+
sb.append("; Max-Age=").append(builder.maxAge);
66+
}
67+
68+
if (builder.httpOnly) {
69+
sb.append("; HttpOnly");
70+
}
71+
72+
if (builder.partitioned) {
73+
sb.append("; Partitioned");
74+
}
75+
76+
if (builder.path != null) {
77+
sb.append("; Path=").append(builder.path);
78+
}
79+
80+
if (builder.sameSite != null) {
81+
sb.append("; SameSite=").append(builder.sameSite);
82+
}
83+
84+
if (builder.secure) {
85+
sb.append("; Secure");
86+
}
87+
88+
payload.setHeader("Set-Cookie", sb);
89+
original.build(payload);
90+
}
91+
}

0 commit comments

Comments
 (0)