Skip to content

Commit 2a43598

Browse files
committed
Added cookies(), formData(), and more useful methods, better cache control and cookie setting
1 parent 8bcddaa commit 2a43598

15 files changed

+238
-58
lines changed

src/main/java/dev/latvian/apps/tinyserver/HTTPServer.java

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
import dev.latvian.apps.tinyserver.http.HTTPPathHandler;
88
import dev.latvian.apps.tinyserver.http.HTTPRequest;
99
import dev.latvian.apps.tinyserver.http.Header;
10-
import dev.latvian.apps.tinyserver.http.response.HTTPResponseBuilder;
10+
import dev.latvian.apps.tinyserver.http.response.HTTPPayload;
1111
import dev.latvian.apps.tinyserver.http.response.HTTPStatus;
1212
import dev.latvian.apps.tinyserver.ws.WSEndpointHandler;
1313
import dev.latvian.apps.tinyserver.ws.WSHandler;
@@ -170,6 +170,7 @@ private void handleClient(Socket socket) {
170170
InputStream in = null;
171171
OutputStream out = null;
172172
WSSession<REQ> upgradedToWebSocket = null;
173+
long startTime = System.currentTimeMillis();
173174

174175
try {
175176
in = new BufferedInputStream(socket.getInputStream(), bufferSize);
@@ -293,13 +294,13 @@ private void handleClient(Socket socket) {
293294
} else if (method == HTTPMethod.CONNECT) {
294295
// no-op
295296
} else {
296-
HTTPResponseBuilder builder = null;
297+
HTTPPayload builder = null;
297298

298299
if (path.isEmpty()) {
299300
var handler = rootHandlers.get(method);
300301

301302
if (handler != null) {
302-
req.init(this, "", new String[0], CompiledPath.EMPTY, headers, queryString, query, in);
303+
req.init(this, startTime, "", new String[0], CompiledPath.EMPTY, headers, queryString, query, in);
303304
builder = createBuilder(req, handler.handler());
304305
}
305306
} else {
@@ -321,14 +322,14 @@ private void handleClient(Socket socket) {
321322
var h = hl.staticHandlers().get(path);
322323

323324
if (h != null) {
324-
req.init(this, path, pathParts, h.path(), headers, queryString, query, in);
325+
req.init(this, startTime, path, pathParts, h.path(), headers, queryString, query, in);
325326
builder = createBuilder(req, h.handler());
326327
} else {
327328
for (var dynamicHandler : hl.dynamicHandlers()) {
328329
var matches = dynamicHandler.path().matches(pathParts);
329330

330331
if (matches != null) {
331-
req.init(this, path, matches, dynamicHandler.path(), headers, queryString, query, in);
332+
req.init(this, startTime, path, matches, dynamicHandler.path(), headers, queryString, query, in);
332333
builder = createBuilder(req, dynamicHandler.handler());
333334
break;
334335
}
@@ -346,7 +347,7 @@ private void handleClient(Socket socket) {
346347
builder.write(out, writeBody);
347348
out.flush();
348349

349-
upgradedToWebSocket = (WSSession) builder.wsSession();
350+
upgradedToWebSocket = (WSSession) builder.getWSSession();
350351

351352
if (upgradedToWebSocket != null) {
352353
upgradedToWebSocket.start(socket, in, out);
@@ -382,18 +383,19 @@ private void handleClient(Socket socket) {
382383
}
383384
}
384385

385-
public HTTPResponseBuilder createBuilder(REQ req, @Nullable HTTPHandler<REQ> handler) {
386-
var builder = new HTTPResponseBuilder();
386+
public HTTPPayload createBuilder(REQ req, @Nullable HTTPHandler<REQ> handler) {
387+
var builder = new HTTPPayload();
387388

388389
if (serverName != null && !serverName.isEmpty()) {
389390
builder.setHeader("Server", serverName);
390391
}
391392

392-
builder.setHeader("Date", HTTPResponseBuilder.DATE_TIME_FORMATTER.format(Instant.now()));
393+
builder.setHeader("Date", HTTPPayload.DATE_TIME_FORMATTER.format(Instant.now()));
393394

394395
if (handler != null) {
395396
try {
396397
var response = handler.handle(req);
398+
req.beforeResponse(builder, response);
397399
builder.setResponse(response);
398400
req.afterResponse(builder, response);
399401
} catch (Exception ex) {

src/main/java/dev/latvian/apps/tinyserver/http/HTTPRequest.java

Lines changed: 75 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,10 @@
33
import dev.latvian.apps.tinyserver.CompiledPath;
44
import dev.latvian.apps.tinyserver.HTTPServer;
55
import dev.latvian.apps.tinyserver.error.InvalidPathException;
6+
import dev.latvian.apps.tinyserver.http.response.HTTPPayload;
67
import dev.latvian.apps.tinyserver.http.response.HTTPResponse;
7-
import dev.latvian.apps.tinyserver.http.response.HTTPResponseBuilder;
8+
import org.jetbrains.annotations.ApiStatus;
9+
import org.jetbrains.annotations.Nullable;
810

911
import java.io.IOException;
1012
import java.io.InputStream;
@@ -16,16 +18,21 @@
1618

1719
public class HTTPRequest {
1820
private HTTPServer<?> server;
21+
private long startTime = 0L;
1922
private String path = "";
2023
private String[] pathParts = new String[0];
2124
private Map<String, String> variables = Map.of();
2225
private String queryString = "";
2326
private Map<String, String> query = Map.of();
2427
private List<Header> headers = List.of();
2528
private InputStream bodyStream = null;
29+
private Map<String, String> cookies = null;
30+
private Map<String, String> formData = null;
2631

27-
public void init(HTTPServer<?> server, String path, String[] pathParts, CompiledPath compiledPath, List<Header> headers, String queryString, Map<String, String> query, InputStream bodyStream) {
32+
@ApiStatus.Internal
33+
public void init(HTTPServer<?> server, long startTime, String path, String[] pathParts, CompiledPath compiledPath, List<Header> headers, String queryString, Map<String, String> query, InputStream bodyStream) {
2834
this.server = server;
35+
this.startTime = startTime;
2936
this.path = path;
3037
this.pathParts = pathParts;
3138

@@ -45,12 +52,20 @@ public void init(HTTPServer<?> server, String path, String[] pathParts, Compiled
4552
this.queryString = queryString;
4653
this.query = query;
4754
this.bodyStream = bodyStream;
55+
afterInit();
56+
}
57+
58+
public void afterInit() {
4859
}
4960

5061
public HTTPServer<?> server() {
5162
return server;
5263
}
5364

65+
public long startTime() {
66+
return startTime;
67+
}
68+
5469
public Map<String, String> variables() {
5570
return variables;
5671
}
@@ -122,9 +137,65 @@ public String body() throws IOException {
122137
return new String(bodyBytes(), StandardCharsets.UTF_8);
123138
}
124139

125-
public void handlePayloadError(HTTPResponseBuilder payload, Exception error) {
140+
public Map<String, String> cookies() {
141+
if (cookies == null) {
142+
cookies = new HashMap<>(4);
143+
144+
for (var header : headers) {
145+
if (header.key().equalsIgnoreCase("Cookie")) {
146+
for (var part : header.value().split("; ")) {
147+
var parts = part.split("=", 2);
148+
149+
if (parts.length == 2) {
150+
cookies.put(parts[0], parts[1]);
151+
}
152+
}
153+
}
154+
}
155+
}
156+
157+
return cookies;
158+
}
159+
160+
@Nullable
161+
public String cookie(String key) {
162+
return cookies().get(key);
163+
}
164+
165+
public Map<String, String> formData() {
166+
if (formData == null) {
167+
formData = new HashMap<>(4);
168+
169+
try {
170+
var body = body();
171+
172+
for (var part : body.split("&")) {
173+
var parts = part.split("=", 2);
174+
175+
if (parts.length == 2) {
176+
formData.put(parts[0], parts[1]);
177+
}
178+
}
179+
} catch (IOException e) {
180+
e.printStackTrace();
181+
}
182+
}
183+
184+
return formData;
185+
}
186+
187+
@Nullable
188+
public String formData(String key) {
189+
return formData().get(key);
190+
}
191+
192+
public void beforeResponse(HTTPPayload payload, HTTPResponse response) {
193+
}
194+
195+
public void afterResponse(HTTPPayload payload, HTTPResponse response) {
126196
}
127197

128-
public void afterResponse(HTTPResponseBuilder payload, HTTPResponse response) {
198+
public void handlePayloadError(HTTPPayload payload, Exception error) {
199+
error.printStackTrace();
129200
}
130201
}

src/main/java/dev/latvian/apps/tinyserver/http/response/ContentResponse.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
public record ContentResponse(HTTPResponse original, ResponseContent body) implements HTTPResponse {
66
@Override
7-
public void build(HTTPResponseBuilder payload) throws Exception {
7+
public void build(HTTPPayload payload) throws Exception {
88
original.build(payload);
99
payload.setBody(body);
1010
}

src/main/java/dev/latvian/apps/tinyserver/http/response/EmptyResponse.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,6 @@ public class EmptyResponse implements HTTPResponse {
44
public static final EmptyResponse INSTANCE = new EmptyResponse();
55

66
@Override
7-
public void build(HTTPResponseBuilder payload) {
7+
public void build(HTTPPayload payload) {
88
}
99
}

src/main/java/dev/latvian/apps/tinyserver/http/response/HTTPResponseBuilder.java renamed to src/main/java/dev/latvian/apps/tinyserver/http/response/HTTPPayload.java

Lines changed: 79 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,17 @@
1111
import java.time.ZoneId;
1212
import java.time.format.DateTimeFormatter;
1313
import java.util.ArrayList;
14+
import java.util.HashMap;
1415
import java.util.List;
1516
import java.util.Locale;
17+
import java.util.Map;
1618

17-
public class HTTPResponseBuilder {
19+
public class HTTPPayload {
1820
public static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("EEE, dd MMM yyyy HH:mm:ss z", Locale.ENGLISH).withZone(ZoneId.of("GMT"));
1921
private static final byte[] CRLF = "\r\n".getBytes(StandardCharsets.UTF_8);
2022

23+
private String cacheControl = "";
24+
private Map<String, String> cookies;
2125
private HTTPStatus status = HTTPStatus.NO_CONTENT;
2226
private final List<Header> headers = new ArrayList<>();
2327
private ResponseContent body = null;
@@ -27,19 +31,91 @@ public void setStatus(HTTPStatus status) {
2731
this.status = status;
2832
}
2933

30-
public void setHeader(String header, Object value) {
34+
public HTTPStatus getStatus() {
35+
return status;
36+
}
37+
38+
public void addHeader(String header, Object value) {
3139
this.headers.add(new Header(header, String.valueOf(value)));
3240
}
3341

42+
public void setHeader(String header, Object value) {
43+
this.headers.removeIf(h -> h.key().equalsIgnoreCase(header));
44+
addHeader(header, value);
45+
}
46+
47+
@Nullable
48+
public String getHeader(String header) {
49+
for (var h : headers) {
50+
if (h.key().equalsIgnoreCase(header)) {
51+
return h.value();
52+
}
53+
}
54+
55+
return null;
56+
}
57+
58+
public void setCacheControl(String cacheControl) {
59+
this.cacheControl = cacheControl;
60+
}
61+
62+
public String getCacheControl() {
63+
return cacheControl;
64+
}
65+
66+
public void setCookie(String key, String value) {
67+
if (cookies == null) {
68+
cookies = new HashMap<>(1);
69+
}
70+
71+
cookies.put(key, value);
72+
}
73+
74+
@Nullable
75+
public String getCookie(String key) {
76+
return cookies == null ? null : cookies.get(key);
77+
}
78+
3479
public void setBody(ResponseContent body) {
3580
this.body = body;
3681
}
3782

83+
@Nullable
84+
public ResponseContent getBody() {
85+
return body;
86+
}
87+
88+
@Nullable
89+
public WSSession<?> getWSSession() {
90+
return wsSession;
91+
}
92+
93+
public void setResponse(HTTPResponse response) throws Exception {
94+
response.build(this);
95+
96+
if (response instanceof WSResponse res) {
97+
wsSession = res.session();
98+
}
99+
}
100+
38101
public void write(OutputStream out, boolean writeBody) throws Exception {
39102
out.write(status.responseBytes);
40103
out.write(CRLF);
41104

42-
for (var header : headers) {
105+
var actualHeaders = new ArrayList<Header>(headers.size() + (cookies == null ? 0 : cookies.size()) + (cacheControl.isEmpty() ? 0 : 1));
106+
actualHeaders.addAll(headers);
107+
108+
if (cookies != null) {
109+
for (var cookie : cookies.entrySet()) {
110+
actualHeaders.add(new Header("Set-Cookie", cookie.getKey() + "=" + cookie.getValue()));
111+
}
112+
}
113+
114+
if (!cacheControl.isEmpty()) {
115+
actualHeaders.add(new Header("Cache-Control", cacheControl));
116+
}
117+
118+
for (var header : actualHeaders) {
43119
out.write((header.key() + ": " + header.value()).getBytes());
44120
out.write(CRLF);
45121
}
@@ -65,17 +141,4 @@ public void write(OutputStream out, boolean writeBody) throws Exception {
65141
body.write(out);
66142
}
67143
}
68-
69-
public void setResponse(HTTPResponse response) throws Exception {
70-
response.build(this);
71-
72-
if (response instanceof WSResponse res) {
73-
wsSession = res.session();
74-
}
75-
}
76-
77-
@Nullable
78-
public WSSession<?> wsSession() {
79-
return wsSession;
80-
}
81144
}

0 commit comments

Comments
 (0)