Compare commits
No commits in common. "main" and "v1.3.0" have entirely different histories.
|
@ -10,7 +10,7 @@ implemented so far.
|
||||||
|
|
||||||
## HTTP/1.1
|
## HTTP/1.1
|
||||||
|
|
||||||
Use the `TaskPoolHttp1Transport` implementation of `HttpTransport` to serve content
|
Use the `Http1Transport` implementation of `HttpTransport` to serve content
|
||||||
using the HTTP/1.1 protocol. See the example below:
|
using the HTTP/1.1 protocol. See the example below:
|
||||||
|
|
||||||
```d
|
```d
|
||||||
|
|
3
dub.json
3
dub.json
|
@ -4,7 +4,8 @@
|
||||||
],
|
],
|
||||||
"copyright": "Copyright © 2024, Andrew Lalis",
|
"copyright": "Copyright © 2024, Andrew Lalis",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"handy-http-primitives": "~>1.8",
|
"handy-http-primitives": "~>1.6",
|
||||||
|
"photon": "~>0.11",
|
||||||
"streams": "~>3.6",
|
"streams": "~>3.6",
|
||||||
"slf4d": "~>4.0"
|
"slf4d": "~>4.0"
|
||||||
},
|
},
|
||||||
|
|
|
@ -1,7 +1,9 @@
|
||||||
{
|
{
|
||||||
"fileVersion": 1,
|
"fileVersion": 1,
|
||||||
"versions": {
|
"versions": {
|
||||||
"handy-http-primitives": "1.8.0",
|
"handy-http-primitives": "1.6.0",
|
||||||
|
"photon": "0.11.0",
|
||||||
|
"sharded-map": "2.7.0",
|
||||||
"slf4d": "4.1.1",
|
"slf4d": "4.1.1",
|
||||||
"streams": "3.6.0"
|
"streams": "3.6.0"
|
||||||
}
|
}
|
||||||
|
|
|
@ -47,16 +47,3 @@ class TaskPoolHttp1Transport : Http1Transport {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
unittest {
|
|
||||||
import slf4d.default_provider;
|
|
||||||
auto logProvider = DefaultProvider.builder().withRootLoggingLevel(Levels.DEBUG).build();
|
|
||||||
configureLoggingProvider(logProvider);
|
|
||||||
|
|
||||||
HttpRequestHandler handler = HttpRequestHandler.of(
|
|
||||||
(ref ServerHttpRequest request, ref ServerHttpResponse response) {
|
|
||||||
response.status = HttpStatus.OK;
|
|
||||||
response.writeBodyString("Testing");
|
|
||||||
});
|
|
||||||
testHttp1Transport(new TaskPoolHttp1Transport(handler));
|
|
||||||
}
|
|
||||||
|
|
|
@ -12,6 +12,7 @@ import handy_http_primitives.address;
|
||||||
|
|
||||||
import streams;
|
import streams;
|
||||||
import slf4d;
|
import slf4d;
|
||||||
|
import photon;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Base class for HTTP/1.1 transport, where different subclasses can define
|
* Base class for HTTP/1.1 transport, where different subclasses can define
|
||||||
|
@ -33,7 +34,6 @@ abstract class Http1Transport : HttpTransport {
|
||||||
}
|
}
|
||||||
|
|
||||||
void start() {
|
void start() {
|
||||||
infoF!"Starting Http1Transport server on port %d."(port);
|
|
||||||
atomicStore(running, true);
|
atomicStore(running, true);
|
||||||
runServer();
|
runServer();
|
||||||
}
|
}
|
||||||
|
@ -41,67 +41,10 @@ abstract class Http1Transport : HttpTransport {
|
||||||
protected abstract void runServer();
|
protected abstract void runServer();
|
||||||
|
|
||||||
void stop() {
|
void stop() {
|
||||||
infoF!"Stopping Http1Transport server on port %d."(port);
|
|
||||||
atomicStore(running, false);
|
atomicStore(running, false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
version(unittest) {
|
|
||||||
/**
|
|
||||||
* A generic test to ensure that any Http1Transport implementation behaves
|
|
||||||
* properly to start & stop, and process requests when running.
|
|
||||||
*
|
|
||||||
* It's assumed that the given transport is configured to run on localhost,
|
|
||||||
* port 8080, and return a standard 200 OK empty response to all requests.
|
|
||||||
* Params:
|
|
||||||
* transport = The transport implementation to test.
|
|
||||||
*/
|
|
||||||
void testHttp1Transport(Http1Transport transport) {
|
|
||||||
import core.thread;
|
|
||||||
import std.string;
|
|
||||||
infoF!"Testing Http1Transport implementation: %s"(transport);
|
|
||||||
|
|
||||||
Thread thread = transport.startInNewThread();
|
|
||||||
Thread.sleep(msecs(100));
|
|
||||||
|
|
||||||
Socket clientSocket1 = new TcpSocket(new InternetAddress(8080));
|
|
||||||
const requestBody = "POST /users HTTP/1.1\r\n" ~
|
|
||||||
"Host: example.com\r\n" ~
|
|
||||||
"Content-Type: text/plain\r\n" ~
|
|
||||||
"Content-Length: 13\r\n" ~
|
|
||||||
"\r\n" ~
|
|
||||||
"Hello, world!";
|
|
||||||
ptrdiff_t bytesSent = clientSocket1.send(requestBody);
|
|
||||||
assert(bytesSent == requestBody.length, "Couldn't send the full request body to the server.");
|
|
||||||
|
|
||||||
ubyte[8192] buffer;
|
|
||||||
size_t totalBytesReceived = 0;
|
|
||||||
ptrdiff_t bytesReceived;
|
|
||||||
do {
|
|
||||||
bytesReceived = clientSocket1.receive(buffer[totalBytesReceived .. $]);
|
|
||||||
if (bytesReceived == Socket.ERROR) {
|
|
||||||
assert(false, "Socket error when attempting to receive a response from the HttpTransport server.");
|
|
||||||
}
|
|
||||||
totalBytesReceived += bytesReceived;
|
|
||||||
} while (bytesReceived > 0);
|
|
||||||
|
|
||||||
string httpResponseContent = cast(string) buffer[0 .. totalBytesReceived];
|
|
||||||
string[] parts = httpResponseContent.split("\r\n\r\n");
|
|
||||||
assert(parts.length > 0, "HTTP 1.1 response is missing required status and headers section.");
|
|
||||||
string[] headerLines = parts[0].split("\r\n");
|
|
||||||
assert(headerLines.length > 0, "HTTP 1.1 response is missing required status line.");
|
|
||||||
string statusLine = headerLines[0];
|
|
||||||
string[] statusLineParts = statusLine.split(" ");
|
|
||||||
assert(statusLineParts[0] == "HTTP/1.1");
|
|
||||||
assert(statusLineParts[1] == "200");
|
|
||||||
assert(statusLineParts[2] == "OK");
|
|
||||||
|
|
||||||
info("Testing is complete. Stopping the server.");
|
|
||||||
transport.stop();
|
|
||||||
thread.join();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The main logic for handling an incoming request from a client. It involves
|
* The main logic for handling an incoming request from a client. It involves
|
||||||
* reading bytes from the client, parsing them as an HTTP request, passing that
|
* reading bytes from the client, parsing them as an HTTP request, passing that
|
||||||
|
@ -112,32 +55,31 @@ version(unittest) {
|
||||||
* requestHandler = The request handler that will handle the received HTTP request.
|
* requestHandler = The request handler that will handle the received HTTP request.
|
||||||
*/
|
*/
|
||||||
void handleClient(Socket clientSocket, HttpRequestHandler requestHandler) {
|
void handleClient(Socket clientSocket, HttpRequestHandler requestHandler) {
|
||||||
SocketInputStream* inputStream = new SocketInputStream(clientSocket);
|
auto inputStream = SocketInputStream(clientSocket);
|
||||||
BufferedInputStream!(SocketInputStream*, 8192)* bufferedInput
|
auto bufferedInput = bufferedInputStreamFor!(8192)(inputStream);
|
||||||
= new BufferedInputStream!(SocketInputStream*, 8192)(inputStream);
|
|
||||||
// Get remote address from the socket.
|
// Get remote address from the socket.
|
||||||
import handy_http_primitives.address;
|
import handy_http_primitives.address;
|
||||||
ClientAddress addr = getAddress(clientSocket);
|
ClientAddress addr = getAddress(clientSocket);
|
||||||
traceF!"Got request from client: %s"(addr.toString());
|
debugF!"Handling client request from %s."(addr.toString());
|
||||||
auto result = readHttpRequest(bufferedInput, addr);
|
auto result = readHttpRequest(&bufferedInput, addr);
|
||||||
|
debug_("Finished reading HTTP request from client.");
|
||||||
if (result.hasError) {
|
if (result.hasError) {
|
||||||
if (result.error.code != -1) {
|
if (result.error.code != -1) {
|
||||||
// Only warn if we didn't read an empty request.
|
// Only warn if we didn't read an empty request.
|
||||||
warnF!"Failed to read request: %s"(result.error.message);
|
warnF!"Failed to read HTTP request: %s"(result.error.message);
|
||||||
}
|
}
|
||||||
inputStream.closeStream();
|
inputStream.closeStream();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
scope ServerHttpRequest request = result.request;
|
scope ServerHttpRequest request = result.request;
|
||||||
scope ServerHttpResponse response;
|
scope ServerHttpResponse response;
|
||||||
SocketOutputStream* outputStream = new SocketOutputStream(clientSocket);
|
SocketOutputStream outputStream = SocketOutputStream(clientSocket);
|
||||||
response.outputStream = outputStreamObjectFor(HttpResponseOutputStream!(SocketOutputStream*)(
|
response.outputStream = outputStreamObjectFor(HttpResponseOutputStream!(SocketOutputStream*)(
|
||||||
outputStream,
|
&outputStream,
|
||||||
&response
|
&response
|
||||||
));
|
));
|
||||||
try {
|
try {
|
||||||
requestHandler.handle(request, response);
|
requestHandler.handle(request, response);
|
||||||
debugF!"%s %s -> %d %s"(request.method, request.url, response.status.code, response.status.text);
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
error("Exception thrown while handling request.", e);
|
error("Exception thrown while handling request.", e);
|
||||||
} catch (Throwable t) {
|
} catch (Throwable t) {
|
||||||
|
|
Loading…
Reference in New Issue