Skip to content

Commit 9cd7a0c

Browse files
committed
Started to implement a custom http server as an alternative to jetty
1 parent cdb5691 commit 9cd7a0c

File tree

5 files changed

+368
-0
lines changed

5 files changed

+368
-0
lines changed
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
package org.javawebstack.httpserver;
2+
3+
public enum HTTPMethod {
4+
5+
GET,
6+
POST,
7+
PUT,
8+
PATCH,
9+
DELETE,
10+
HEAD,
11+
OPTIONS,
12+
TRACE,
13+
CONNECT
14+
15+
}
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
package org.javawebstack.httpserver;
2+
3+
public enum HTTPStatus {
4+
5+
OK(200, "OK"),
6+
CREATED(201, "Created"),
7+
ACCEPTED(202, "Accepted"),
8+
NON_AUTHORITATIVE_INFORMATION(203, "Non-Authoritative Information"),
9+
NO_CONTENT(204, "No Content"),
10+
RESET_CONTENT(205, "Reset Content"),
11+
PARTIAL_CONTENT(206, "Partial Content"),
12+
MULTI_STATUS(207, "Multi-Status"),
13+
ALREADY_REPORTED(208, "Already Reported"),
14+
IM_USED(226, "IM Used"),
15+
16+
MULTIPLE_CHOICES(300, "Multiple Choices"),
17+
MOVED_PERMANENTLY(301, "Moved Permanently"),
18+
FOUND(302, "Found"),
19+
SEE_OTHER(303, "See Other"),
20+
NOT_MODIFIED(304, "Not Modified"),
21+
USE_PROXY(305, "Use Proxy"),
22+
TEMPORARY_REDIRECT(307, "Temporary Redirect"),
23+
PERMANENT_REDIRECT(308, "Permanent Redirect"),
24+
25+
BAD_REQUEST(400, "Bad Request"),
26+
UNAUTHORIZED(401, "Unauthorized"),
27+
PAYMENT_REQUIRED(402, "Payment Required"),
28+
FORBIDDEN(403, "Forbidden"),
29+
NOT_FOUND(404, "Not Found"),
30+
METHOD_NOT_ALLOWED(405, "Method Not Allowed"),
31+
NOT_ACCEPTABLE(406, "Not Acceptable"),
32+
PROXY_AUTHENTICATION_REQUIRED(407, "Proxy Authentication Required"),
33+
REQUEST_TIMEOUT(408, "Request Timeout"),
34+
CONFLICT(409, "Conflict"),
35+
GONE(410, "Gone"),
36+
LENGTH_REQUIRED(411, "Length Required"),
37+
PRECONDITION_FAILED(412, "Precondition Failed"),
38+
REQUEST_ENTITY_TOO_LARGE(413, "Request Entity Too Large"),
39+
REQUEST_URI_TOO_LONG(414, "Request-URI Too Long"),
40+
UNSUPPORTED_MEDIA_TYPE(415, "Unsupported Media Type"),
41+
REQUEST_RANGE_NOT_SATISFIABLE(416, "Requested Range Not Satisfiable"),
42+
EXPECTATION_FAILED(417, "Expectation Failed"),
43+
IM_A_TEAPOT(418, "I'm a teapot"),
44+
UNPROCESSABLE_ENTITY(422, "Unprocessable Entity"),
45+
LOCKED(423, "Locked"),
46+
FAILED_DEPENDENCY(424, "Failed Dependency"),
47+
UPGRADE_REQUIRED(426, "Upgrade Required"),
48+
PRECONDITION_REQUIRED(428, "Precondition Required"),
49+
TOO_MANY_REQUESTS(429, "Too Many Requests"),
50+
REQUEST_HEADER_FIELDS_TOO_LARGE(431, "Request Header Fields Too Large"),
51+
UNAVAILABLE_FOR_LEGAL_REASONS(451, "Unavailable For Legal Reasons"),
52+
53+
INTERNAL_SERVER_ERROR(500, "Internal Server Error"),
54+
NOT_IMPLEMENTED(501, "Not Implemented"),
55+
BAD_GATEWAY(502, "Bad Gateway"),
56+
SERVICE_UNAVAILABLE(503, "Service Unavailable"),
57+
GATEWAY_TIMEOUT(504, "Gateway Timeout"),
58+
HTTP_VERSION_NOT_SUPPORTED(505, "HTTP Version Not Supported"),
59+
VARIANT_ALSO_NEGOTIATES(506, "Variant Also Negotiates"),
60+
INSUFFICIENT_STORAGE(507, "Insufficient Storage"),
61+
LOOP_DETECTED(508, "Loop Detected"),
62+
BANDWIDTH_LIMIT_EXCEEDED(509, "Bandwidth Limit Exceeded"),
63+
NOT_EXTENDED(510, "Not Extended"),
64+
NETWORK_AUTHENTICATION_REQUIRED(511, "Network Authentication Required");
65+
66+
private final int status;
67+
private final String message;
68+
69+
HTTPStatus(int status, String message) {
70+
this.status = status;
71+
this.message = message;
72+
}
73+
74+
public int getStatus() {
75+
return status;
76+
}
77+
78+
public String getMessage() {
79+
return message;
80+
}
81+
82+
public static HTTPStatus byStatus(int status) {
83+
for(HTTPStatus s : values()) {
84+
if(s.status == status)
85+
return s;
86+
}
87+
return null;
88+
}
89+
90+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
package org.javawebstack.httpserver.socket;
2+
3+
import java.io.IOException;
4+
import java.net.ServerSocket;
5+
import java.net.Socket;
6+
import java.nio.charset.StandardCharsets;
7+
8+
public class HTTPServerSocket {
9+
10+
private final ServerSocket serverSocket;
11+
12+
public HTTPServerSocket(int port) throws IOException {
13+
serverSocket = new ServerSocket(port);
14+
}
15+
16+
public void close() throws IOException {
17+
serverSocket.close();
18+
}
19+
20+
public boolean isClosed() {
21+
return serverSocket.isClosed();
22+
}
23+
24+
public HTTPSocket accept() throws IOException {
25+
Socket socket = serverSocket.accept();
26+
return new HTTPSocket(socket);
27+
}
28+
29+
}
Lines changed: 183 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,183 @@
1+
package org.javawebstack.httpserver.socket;
2+
3+
import org.javawebstack.httpserver.HTTPMethod;
4+
import org.javawebstack.httpserver.HTTPStatus;
5+
6+
import java.io.ByteArrayOutputStream;
7+
import java.io.IOException;
8+
import java.io.InputStream;
9+
import java.io.OutputStream;
10+
import java.net.Socket;
11+
import java.nio.charset.StandardCharsets;
12+
import java.util.*;
13+
14+
public class HTTPSocket {
15+
16+
private final Socket socket;
17+
private final InputStream inputStream;
18+
private final OutputStream outputStream;
19+
private final HTTPMethod requestMethod;
20+
private final String requestPath;
21+
private String requestQuery;
22+
private final String requestVersion;
23+
private final Map<String, List<String>> requestHeaders = new HashMap<>();
24+
private final Map<String, List<String>> responseHeaders = new LinkedHashMap<>();
25+
private int responseStatus = 200;
26+
private String responseStatusMessage = "OK";
27+
private boolean headersSent;
28+
29+
public HTTPSocket(Socket socket) throws IOException {
30+
this.socket = socket;
31+
this.inputStream = socket.getInputStream();
32+
this.outputStream = socket.getOutputStream();
33+
socket.getOutputStream().flush();
34+
ByteArrayOutputStream baos = new ByteArrayOutputStream();
35+
int lb = -1;
36+
while (true) {
37+
int b = inputStream.read();
38+
if(b == -1) {
39+
socket.close();
40+
throw new IOException("Unexpected end of stream");
41+
}
42+
if(b == '\r' && lb == '\n') {
43+
b = inputStream.read();
44+
break;
45+
}
46+
baos.write(b);
47+
lb = b;
48+
}
49+
String[] lines = new String(baos.toByteArray(), StandardCharsets.UTF_8).split("\\r?\\n");
50+
if(lines.length < 2) {
51+
socket.close();
52+
throw new IOException("Invalid http request");
53+
}
54+
String[] first = lines[0].split(" ");
55+
if(first.length != 3 || !first[1].startsWith("/")) {
56+
socket.close();
57+
throw new IOException("Invalid http request");
58+
}
59+
requestMethod = HTTPMethod.valueOf(first[0]);
60+
String[] pathSplit = first[1].split("\\?", 2);
61+
requestPath = pathSplit[0];
62+
if(pathSplit.length == 2)
63+
requestQuery = pathSplit[1];
64+
requestVersion = first[2];
65+
if(!requestVersion.equals("HTTP/1.1") && !requestVersion.equals("HTTP/1.0")) {
66+
setResponseStatus(HTTPStatus.HTTP_VERSION_NOT_SUPPORTED);
67+
writeHeaders();
68+
close();
69+
throw new IOException("Unsupported http version");
70+
}
71+
for(int i=1; i<lines.length; i++) {
72+
if(lines[i].length() == 0)
73+
continue;
74+
String[] hspl = lines[i].split(": ", 2);
75+
if(hspl.length != 2)
76+
throw new IOException("Invalid http request");
77+
List<String> values = requestHeaders.computeIfAbsent(hspl[0].toLowerCase(Locale.ROOT), h -> new ArrayList<>());
78+
values.add(hspl[1]);
79+
}
80+
}
81+
82+
public HTTPSocket setResponseStatus(HTTPStatus status) {
83+
return setResponseStatus(status.getStatus(), status.getMessage());
84+
}
85+
86+
public HTTPSocket setResponseStatus(int status) {
87+
HTTPStatus s = HTTPStatus.byStatus(status);
88+
return setResponseStatus(status, s != null ? s.getMessage() : "Unknown");
89+
}
90+
91+
public HTTPSocket setResponseStatus(int status, String message) {
92+
this.responseStatus = status;
93+
this.responseStatusMessage = message;
94+
return this;
95+
}
96+
97+
public HTTPSocket setResponseHeader(String name, String value) {
98+
responseHeaders.put(name.toLowerCase(Locale.ROOT), Arrays.asList(value));
99+
return this;
100+
}
101+
102+
public HTTPSocket addResponseHeader(String name, String value) {
103+
responseHeaders.computeIfAbsent(name.toLowerCase(Locale.ROOT), h -> new ArrayList<>()).add(value);
104+
return this;
105+
}
106+
107+
public void close() throws IOException {
108+
socket.close();
109+
}
110+
111+
public void writeHeaders() throws IOException {
112+
headersSent = true;
113+
StringBuilder sb = new StringBuilder(requestVersion)
114+
.append(' ')
115+
.append(responseStatus)
116+
.append(' ')
117+
.append(responseStatusMessage)
118+
.append("\r\n");
119+
responseHeaders.forEach((k, l) -> l.forEach(v -> sb.append(k.toLowerCase(Locale.ROOT)).append(": ").append(v).append("\r\n")));
120+
sb.append("\r\n");
121+
outputStream.write(sb.toString().getBytes(StandardCharsets.UTF_8));
122+
outputStream.flush();
123+
}
124+
125+
public InputStream getInputStream() {
126+
return inputStream;
127+
}
128+
129+
public OutputStream getOutputStream() {
130+
return new HTTPOutputStream();
131+
}
132+
133+
public HTTPMethod getRequestMethod() {
134+
return requestMethod;
135+
}
136+
137+
public String getRequestPath() {
138+
return requestPath;
139+
}
140+
141+
public String getRequestQuery() {
142+
return requestQuery;
143+
}
144+
145+
public String getRequestVersion() {
146+
return requestVersion;
147+
}
148+
149+
public Map<String, List<String>> getRequestHeaders() {
150+
return requestHeaders;
151+
}
152+
153+
public Map<String, List<String>> getResponseHeaders() {
154+
return responseHeaders;
155+
}
156+
157+
public int getResponseStatus() {
158+
return responseStatus;
159+
}
160+
161+
public String getResponseStatusMessage() {
162+
return responseStatusMessage;
163+
}
164+
165+
public boolean isClosed() {
166+
return socket.isClosed();
167+
}
168+
169+
private class HTTPOutputStream extends OutputStream {
170+
public void write(int i) throws IOException {
171+
if(!headersSent)
172+
writeHeaders();
173+
outputStream.write(i);
174+
}
175+
public void close() throws IOException {
176+
outputStream.close();
177+
}
178+
public void flush() throws IOException {
179+
outputStream.flush();
180+
}
181+
}
182+
183+
}
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
package org.javawebstack.httpserver.socket;
2+
3+
import java.io.IOException;
4+
import java.util.concurrent.ExecutorService;
5+
import java.util.concurrent.Executors;
6+
import java.util.function.Consumer;
7+
8+
public class HTTPSocketWorker {
9+
10+
private final Thread schedulerThread;
11+
private ExecutorService executorService;
12+
private final HTTPServerSocket serverSocket;
13+
14+
public HTTPSocketWorker(HTTPServerSocket serverSocket, Consumer<HTTPSocket> handler) {
15+
this.serverSocket = serverSocket;
16+
this.schedulerThread = new Thread(() -> {
17+
while (!serverSocket.isClosed()) {
18+
try {
19+
HTTPSocket socket = serverSocket.accept();
20+
executorService.execute(() -> {
21+
try {
22+
handler.accept(socket);
23+
if(!socket.isClosed())
24+
socket.close();
25+
} catch (IOException ex) {}
26+
});
27+
} catch (IOException exception) {}
28+
}
29+
});
30+
}
31+
32+
public HTTPSocketWorker start() {
33+
this.executorService = Executors.newCachedThreadPool();
34+
this.schedulerThread.start();
35+
return this;
36+
}
37+
38+
public void join() {
39+
try {
40+
schedulerThread.join();
41+
} catch (InterruptedException e) {}
42+
}
43+
44+
public void stop() {
45+
this.executorService.shutdown();
46+
try {
47+
this.serverSocket.close();
48+
} catch (IOException e) {}
49+
}
50+
51+
}

0 commit comments

Comments
 (0)