-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathHttpServer.java
More file actions
220 lines (174 loc) · 5.32 KB
/
HttpServer.java
File metadata and controls
220 lines (174 loc) · 5.32 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
package net.gescobar.httpserver;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketTimeoutException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* <p>An HTTP Server that receives and parses HTTP requests. When a connection is received, a {@link HttpConnection}
* is created to handle the request in a separate thread - using a thread pool.</p>
*
* <p>Starting the HTTP Server is as simple as instantiating this class and calling the {@link #start} method:</p>
*
* <code>
* HttpServer server = new HttpServer();
* server.start();
* ...
*
* // somewhere else
* server.stop();
* </code>
*
* <p>To process HTTP requests you will need to provide an implementation of the {@link Handler} interface using the
* constructor {@link #HttpServer(int, Handler)} or the {@link #setHandler(Handler)} method. If no {@link Handler}
* is specified, a default implementation is provided that always returns a status 200 (OK) with no content.
*
* @author German Escobar
*/
public class HttpServer {
private final Logger log = LoggerFactory.getLogger(HttpServer.class);
/**
* The default port to use unless other is specified.
*/
private static final int DEFAULT_PORT = 3000;
/**
* The executor in which the requests are handled.
*/
private ExecutorService executor = Executors.newFixedThreadPool(10);
/**
* Used to listen in the specified port.
*/
private ServerSocket serverSocket;
/**
* The thread that is constantly accepting connections. It delegates them to a {@link HttpConnection}.
*/
private ServerDaemon serverDaemon;
/**
* The port in which this server is going to listen.
*/
private int port;
/**
* The implementation that will handle requests.
*/
private Handler handler;
/**
* Tells if the server is running or not.
*/
private volatile boolean running;
/**
* Constructor. Initializes the server with the default port and {@link Handler} implementation.
*/
public HttpServer() {
this(DEFAULT_PORT);
}
/**
* Constructor. Initializes the server with the specified port and default {@link Handler} implementation.
*
* @param port the port in which the server should listen.
*/
public HttpServer(int port) {
this(new Handler() {
@Override
public void handle(Request request, Response response) {}
}, port);
}
/**
* Constructor. Initializes the server with the specified {@link Handler} implementation and default port.
*
* @param handler the {@link Handler} implementation that will handle the requests.
*/
public HttpServer(Handler handler) {
this(handler, DEFAULT_PORT);
}
/**
* Constructor. Initializes the server with the specified port and {@link Handler} implementation.
*
* @param handler the {@link Handler} implementation that will handle the requests.
* @param port the port in which the server should listen.
*/
public HttpServer(Handler handler, int port) {
this.port = port;
this.handler = handler;
}
/**
* Starts the HTTP Server.
*
* @return itself for method chaining
* @throws IOException if an error occurs in the underlying connection.
*/
public HttpServer start() throws IOException {
if (running) {
log.warn("HTTP Server already running ... ");
return this;
}
log.debug("starting the HTTP Server ... ");
running = true;
serverSocket = new ServerSocket(port);
serverSocket.setSoTimeout(500);
serverDaemon = new ServerDaemon();
serverDaemon.start();
log.info("<< HTTP Server running on port " + port + " >>");
return this;
}
/**
* Stops the server gracefully.
*/
public void stop() {
// signals the daemon thread to stop the loop and terminate the execution.
log.debug("stopping the HTTP Server ... ");
running = false;
// wait until it actually stops
try { serverDaemon.join(); } catch (InterruptedException e) {}
}
/**
* This is the main thread listening for new connections and delegating them to {@link HttpConnection} objects.
*
* @author German Escobar
*/
private class ServerDaemon extends Thread {
@Override
public void run() {
while (running) {
try {
Socket socket = serverSocket.accept();
// this is what actually process the request
executor.execute( new HttpConnection(socket, handler) );
} catch (SocketTimeoutException e) {
// no connection received this time
} catch (IOException e) {
log.error("IOException while accepting connection: " + e.getMessage(), e);
}
}
try {
serverSocket.close();
} catch (IOException e) {
log.error("IOException while stopping server: " + e.getMessage(), e);
}
log.trace("daemon thread is exiting ...");
}
}
public void setExecutor(ExecutorService executor) {
this.executor = executor;
}
public void setPort(int port) {
this.port = port;
}
public void setHandler(Handler handler) {
this.handler = handler;
}
public void setRunning(boolean running) {
this.running = running;
}
public static void main(String...args) throws IOException {
final HttpServer server = new HttpServer().start();
Runtime.getRuntime().addShutdownHook(new Thread() {
@Override
public synchronized void start() {
server.stop();
}
});
}
}