mirror of https://github.com/adamdruppe/arsd.git
gzip and openssl support
This commit is contained in:
parent
88ab63e48f
commit
40b737eb9d
198
http2.d
198
http2.d
|
@ -294,16 +294,21 @@ class HttpRequest {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Socket getOpenSocketOnHost(string host, ushort port) {
|
Socket getOpenSocketOnHost(string host, ushort port, bool ssl) {
|
||||||
Socket openNewConnection() {
|
Socket openNewConnection() {
|
||||||
auto socket = new Socket(AddressFamily.INET, SocketType.STREAM);
|
Socket socket;
|
||||||
|
if(ssl)
|
||||||
|
socket = new SslClientSocket(AddressFamily.INET, SocketType.STREAM);
|
||||||
|
else
|
||||||
|
socket = new Socket(AddressFamily.INET, SocketType.STREAM);
|
||||||
|
|
||||||
socket.connect(new InternetAddress(host, port));
|
socket.connect(new InternetAddress(host, port));
|
||||||
debug writeln("opening to ", host, ":", port);
|
debug writeln("opening to ", host, ":", port);
|
||||||
return socket;
|
return socket;
|
||||||
}
|
}
|
||||||
|
|
||||||
import std.string;
|
import std.string;
|
||||||
auto key = format("%s:%s", host, port);
|
auto key = format("http%s://%s:%s", ssl ? "s" : "", host, port);
|
||||||
|
|
||||||
if(auto hostListing = key in socketsPerHost) {
|
if(auto hostListing = key in socketsPerHost) {
|
||||||
// try to find an available socket that is already open
|
// try to find an available socket that is already open
|
||||||
|
@ -373,7 +378,7 @@ class HttpRequest {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto socket = getOpenSocketOnHost(pc.requestParameters.host, pc.requestParameters.port);
|
auto socket = getOpenSocketOnHost(pc.requestParameters.host, pc.requestParameters.port, pc.requestParameters.ssl);
|
||||||
|
|
||||||
if(socket !is null) {
|
if(socket !is null) {
|
||||||
activeRequestOnSocket[socket] = pc;
|
activeRequestOnSocket[socket] = pc;
|
||||||
|
@ -411,6 +416,7 @@ class HttpRequest {
|
||||||
int inactiveCount = 0;
|
int inactiveCount = 0;
|
||||||
foreach(sock, request; activeRequestOnSocket) {
|
foreach(sock, request; activeRequestOnSocket) {
|
||||||
if(readSet.isSet(sock)) {
|
if(readSet.isSet(sock)) {
|
||||||
|
keep_going:
|
||||||
auto got = sock.receive(buffer);
|
auto got = sock.receive(buffer);
|
||||||
if(got < 0) {
|
if(got < 0) {
|
||||||
throw new Exception("receive error");
|
throw new Exception("receive error");
|
||||||
|
@ -432,6 +438,14 @@ class HttpRequest {
|
||||||
|
|
||||||
if(request.onDataReceived)
|
if(request.onDataReceived)
|
||||||
request.onDataReceived(request);
|
request.onDataReceived(request);
|
||||||
|
|
||||||
|
if(auto s = cast(SslClientSocket) sock) {
|
||||||
|
// select doesn't handle the case with stuff
|
||||||
|
// left in the ssl buffer so i'm checking it separately
|
||||||
|
if(s.dataPending()) {
|
||||||
|
goto keep_going;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if(request.state == State.sendingHeaders || request.state == State.sendingBody)
|
if(request.state == State.sendingHeaders || request.state == State.sendingBody)
|
||||||
|
@ -477,6 +491,9 @@ class HttpRequest {
|
||||||
|
|
||||||
bool closeSocketWhenComplete;
|
bool closeSocketWhenComplete;
|
||||||
|
|
||||||
|
import std.zlib;
|
||||||
|
UnCompress uncompress;
|
||||||
|
|
||||||
void handleIncomingData(scope const ubyte[] dataIn) {
|
void handleIncomingData(scope const ubyte[] dataIn) {
|
||||||
if(state == State.waitingForResponse) {
|
if(state == State.waitingForResponse) {
|
||||||
state = State.readingHeaders;
|
state = State.readingHeaders;
|
||||||
|
@ -529,16 +546,18 @@ class HttpRequest {
|
||||||
case "Transfer-Encoding":
|
case "Transfer-Encoding":
|
||||||
// note that if it is gzipped, it zips first, then chunks the compressed stream.
|
// note that if it is gzipped, it zips first, then chunks the compressed stream.
|
||||||
// so we should always dechunk first, then feed into the decompressor
|
// so we should always dechunk first, then feed into the decompressor
|
||||||
if(value == "chunked")
|
if(value.strip == "chunked")
|
||||||
bodyReadingState.isChunked = true;
|
bodyReadingState.isChunked = true;
|
||||||
else throw new Exception("Unknown Transfer-Encoding: " ~ value);
|
else throw new Exception("Unknown Transfer-Encoding: " ~ value);
|
||||||
break;
|
break;
|
||||||
case "Content-Encoding":
|
case "Content-Encoding":
|
||||||
if(value == "gzip")
|
if(value == "gzip") {
|
||||||
bodyReadingState.isGzipped = true;
|
bodyReadingState.isGzipped = true;
|
||||||
else if(value == "deflate")
|
uncompress = new UnCompress();
|
||||||
|
} else if(value == "deflate") {
|
||||||
bodyReadingState.isDeflated = true;
|
bodyReadingState.isDeflated = true;
|
||||||
else throw new Exception("Unknown Content-Encoding: " ~ value);
|
uncompress = new UnCompress();
|
||||||
|
} else throw new Exception("Unknown Content-Encoding: " ~ value);
|
||||||
break;
|
break;
|
||||||
case "Set-Cookie":
|
case "Set-Cookie":
|
||||||
// FIXME handle
|
// FIXME handle
|
||||||
|
@ -644,32 +663,58 @@ class HttpRequest {
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case 2: // reading data
|
case 2: // reading data
|
||||||
// FIXME: gunzip
|
auto can = a + bodyReadingState.contentLengthRemaining;
|
||||||
responseData.content ~= data[a .. a + bodyReadingState.contentLengthRemaining];
|
if(can > data.length)
|
||||||
|
can = data.length;
|
||||||
|
|
||||||
a += bodyReadingState.contentLengthRemaining;
|
//if(bodyReadingState.isGzipped || bodyReadingState.isDeflated)
|
||||||
|
// responseData.content ~= cast(ubyte[]) uncompress.uncompress(data[a .. can]);
|
||||||
|
//else
|
||||||
|
responseData.content ~= data[a .. can];
|
||||||
|
|
||||||
|
bodyReadingState.contentLengthRemaining -= can - a;
|
||||||
|
a += can - a;
|
||||||
|
if(bodyReadingState.contentLengthRemaining == 0) {
|
||||||
a += 1; // skipping a 13 10
|
a += 1; // skipping a 13 10
|
||||||
data = data[a+1 .. $];
|
|
||||||
bodyReadingState.chunkedState = 0;
|
bodyReadingState.chunkedState = 0;
|
||||||
|
data = data[a+1 .. $];
|
||||||
|
} else {
|
||||||
|
data = data[a .. $];
|
||||||
|
}
|
||||||
goto start_over;
|
goto start_over;
|
||||||
case 3: // reading footers
|
case 3: // reading footers
|
||||||
goto done; // FIXME
|
//goto done; // FIXME
|
||||||
|
state = State.complete;
|
||||||
|
|
||||||
|
auto n = uncompress.uncompress(responseData.content);
|
||||||
|
n ~= uncompress.flush();
|
||||||
|
responseData.content = cast(ubyte[]) n;
|
||||||
|
|
||||||
|
//if(bodyReadingState.isGzipped || bodyReadingState.isDeflated)
|
||||||
|
// responseData.content ~= cast(ubyte[]) uncompress.flush();
|
||||||
|
|
||||||
|
responseData.contentText = cast(string) responseData.content;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
done:
|
done:
|
||||||
state = State.complete;
|
|
||||||
|
|
||||||
responseData.contentText = cast(string) responseData.content;
|
|
||||||
// FIXME
|
// FIXME
|
||||||
//if(closeSocketWhenComplete)
|
//if(closeSocketWhenComplete)
|
||||||
//socket.close();
|
//socket.close();
|
||||||
} else {
|
} else {
|
||||||
// FIXME: gunzip
|
//if(bodyReadingState.isGzipped || bodyReadingState.isDeflated)
|
||||||
|
// responseData.content ~= cast(ubyte[]) uncompress.uncompress(data);
|
||||||
|
//else
|
||||||
responseData.content ~= data;
|
responseData.content ~= data;
|
||||||
assert(data.length <= bodyReadingState.contentLengthRemaining);
|
assert(data.length <= bodyReadingState.contentLengthRemaining);
|
||||||
bodyReadingState.contentLengthRemaining -= data.length;
|
bodyReadingState.contentLengthRemaining -= data.length;
|
||||||
if(bodyReadingState.contentLengthRemaining == 0) {
|
if(bodyReadingState.contentLengthRemaining == 0) {
|
||||||
|
if(bodyReadingState.isGzipped || bodyReadingState.isDeflated) {
|
||||||
|
auto n = uncompress.uncompress(responseData.content);
|
||||||
|
n ~= uncompress.flush();
|
||||||
|
responseData.content = cast(ubyte[]) n;
|
||||||
|
//responseData.content ~= cast(ubyte[]) uncompress.flush();
|
||||||
|
}
|
||||||
state = State.complete;
|
state = State.complete;
|
||||||
responseData.contentText = cast(string) responseData.content;
|
responseData.contentText = cast(string) responseData.content;
|
||||||
// FIXME
|
// FIXME
|
||||||
|
@ -688,8 +733,9 @@ class HttpRequest {
|
||||||
requestParameters.method = method;
|
requestParameters.method = method;
|
||||||
requestParameters.host = parts.host;
|
requestParameters.host = parts.host;
|
||||||
requestParameters.port = cast(ushort) parts.port;
|
requestParameters.port = cast(ushort) parts.port;
|
||||||
|
requestParameters.ssl = parts.scheme == "https";
|
||||||
if(parts.port == 0)
|
if(parts.port == 0)
|
||||||
requestParameters.port = 80; // FIXME: SSL
|
requestParameters.port = requestParameters.ssl ? 443 : 80;
|
||||||
requestParameters.uri = parts.path;
|
requestParameters.uri = parts.path;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -751,7 +797,11 @@ class HttpRequest {
|
||||||
return; // already sent
|
return; // already sent
|
||||||
string headers;
|
string headers;
|
||||||
|
|
||||||
headers ~= to!string(requestParameters.method) ~ " "~requestParameters.uri~" HTTP/1.1\r\n";
|
headers ~= to!string(requestParameters.method) ~ " "~requestParameters.uri;
|
||||||
|
if(requestParameters.useHttp11)
|
||||||
|
headers ~= " HTTP/1.1\r\n";
|
||||||
|
else
|
||||||
|
headers ~= " HTTP/1.0\r\n";
|
||||||
headers ~= "Host: "~requestParameters.host~"\r\n";
|
headers ~= "Host: "~requestParameters.host~"\r\n";
|
||||||
if(requestParameters.userAgent.length)
|
if(requestParameters.userAgent.length)
|
||||||
headers ~= "User-Agent: "~requestParameters.userAgent~"\r\n";
|
headers ~= "User-Agent: "~requestParameters.userAgent~"\r\n";
|
||||||
|
@ -759,6 +809,8 @@ class HttpRequest {
|
||||||
headers ~= "Authorization: "~requestParameters.authorization~"\r\n";
|
headers ~= "Authorization: "~requestParameters.authorization~"\r\n";
|
||||||
if(requestParameters.bodyData.length)
|
if(requestParameters.bodyData.length)
|
||||||
headers ~= "Content-Length: "~to!string(requestParameters.bodyData.length)~"\r\n";
|
headers ~= "Content-Length: "~to!string(requestParameters.bodyData.length)~"\r\n";
|
||||||
|
if(requestParameters.acceptGzip)
|
||||||
|
headers ~= "Accept-Encoding: gzip\r\n";
|
||||||
|
|
||||||
foreach(header; requestParameters.headers)
|
foreach(header; requestParameters.headers)
|
||||||
headers ~= header ~ "\r\n";
|
headers ~= header ~ "\r\n";
|
||||||
|
@ -812,6 +864,8 @@ struct HttpRequestParameters {
|
||||||
ushort port;
|
ushort port;
|
||||||
string uri;
|
string uri;
|
||||||
|
|
||||||
|
bool ssl;
|
||||||
|
|
||||||
string userAgent;
|
string userAgent;
|
||||||
string authorization;
|
string authorization;
|
||||||
|
|
||||||
|
@ -845,7 +899,7 @@ enum HttpVerb { GET, HEAD, POST, PUT, DELETE, OPTIONS, TRACE, CONNECT, PATCH, ME
|
||||||
class HttpClient {
|
class HttpClient {
|
||||||
/* Protocol restrictions, useful to disable when debugging servers */
|
/* Protocol restrictions, useful to disable when debugging servers */
|
||||||
bool useHttp11 = true;
|
bool useHttp11 = true;
|
||||||
bool useGzip = true;
|
bool acceptGzip = true;
|
||||||
|
|
||||||
/// Automatically follow a redirection?
|
/// Automatically follow a redirection?
|
||||||
bool followLocation = false;
|
bool followLocation = false;
|
||||||
|
@ -866,6 +920,9 @@ class HttpClient {
|
||||||
request.requestParameters.userAgent = userAgent;
|
request.requestParameters.userAgent = userAgent;
|
||||||
request.requestParameters.authorization = authorization;
|
request.requestParameters.authorization = authorization;
|
||||||
|
|
||||||
|
request.requestParameters.useHttp11 = this.useHttp11;
|
||||||
|
request.requestParameters.acceptGzip = this.acceptGzip;
|
||||||
|
|
||||||
return request;
|
return request;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -935,14 +992,14 @@ version(testing)
|
||||||
void main() {
|
void main() {
|
||||||
import std.stdio;
|
import std.stdio;
|
||||||
auto client = new HttpClient();
|
auto client = new HttpClient();
|
||||||
auto request = client.navigateTo(Url("http://localhost/chunked.php"));
|
auto request = client.navigateTo(Uri("http://localhost/chunked.php"));
|
||||||
request.send();
|
request.send();
|
||||||
auto request2 = client.navigateTo(Url("http://dlang.org/"));
|
auto request2 = client.navigateTo(Uri("http://dlang.org/"));
|
||||||
request2.send();
|
request2.send();
|
||||||
|
|
||||||
{
|
{
|
||||||
auto response = request2.waitForCompletion();
|
auto response = request2.waitForCompletion();
|
||||||
write(cast(string) response.content);
|
//write(cast(string) response.content);
|
||||||
}
|
}
|
||||||
|
|
||||||
auto response = request.waitForCompletion();
|
auto response = request.waitForCompletion();
|
||||||
|
@ -950,3 +1007,98 @@ void main() {
|
||||||
|
|
||||||
writeln(HttpRequest.socketsPerHost);
|
writeln(HttpRequest.socketsPerHost);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// From sslsocket.d
|
||||||
|
|
||||||
|
version=use_openssl;
|
||||||
|
|
||||||
|
version(use_openssl) {
|
||||||
|
alias SslClientSocket = OpenSslSocket;
|
||||||
|
|
||||||
|
extern(C) {
|
||||||
|
int SSL_library_init();
|
||||||
|
void OpenSSL_add_all_ciphers();
|
||||||
|
void OpenSSL_add_all_digests();
|
||||||
|
void SSL_load_error_strings();
|
||||||
|
|
||||||
|
struct SSL {}
|
||||||
|
struct SSL_CTX {}
|
||||||
|
struct SSL_METHOD {}
|
||||||
|
|
||||||
|
SSL_CTX* SSL_CTX_new(const SSL_METHOD* method);
|
||||||
|
SSL* SSL_new(SSL_CTX*);
|
||||||
|
int SSL_set_fd(SSL*, int);
|
||||||
|
int SSL_connect(SSL*);
|
||||||
|
int SSL_write(SSL*, const void*, int);
|
||||||
|
int SSL_read(SSL*, void*, int);
|
||||||
|
int SSL_pending(const SSL*);
|
||||||
|
void SSL_free(SSL*);
|
||||||
|
void SSL_CTX_free(SSL_CTX*);
|
||||||
|
|
||||||
|
SSL_METHOD* SSLv3_client_method();
|
||||||
|
}
|
||||||
|
|
||||||
|
shared static this() {
|
||||||
|
SSL_library_init();
|
||||||
|
OpenSSL_add_all_ciphers();
|
||||||
|
OpenSSL_add_all_digests();
|
||||||
|
SSL_load_error_strings();
|
||||||
|
}
|
||||||
|
|
||||||
|
pragma(lib, "crypto");
|
||||||
|
pragma(lib, "ssl");
|
||||||
|
|
||||||
|
class OpenSslSocket : Socket {
|
||||||
|
private SSL* ssl;
|
||||||
|
private SSL_CTX* ctx;
|
||||||
|
private void initSsl() {
|
||||||
|
ctx = SSL_CTX_new(SSLv3_client_method());
|
||||||
|
assert(ctx !is null);
|
||||||
|
|
||||||
|
ssl = SSL_new(ctx);
|
||||||
|
SSL_set_fd(ssl, this.handle);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool dataPending() {
|
||||||
|
return SSL_pending(ssl) > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@trusted
|
||||||
|
override void connect(Address to) {
|
||||||
|
super.connect(to);
|
||||||
|
if(SSL_connect(ssl) == -1)
|
||||||
|
throw new Exception("ssl connect");
|
||||||
|
}
|
||||||
|
|
||||||
|
@trusted
|
||||||
|
override ptrdiff_t send(const(void)[] buf, SocketFlags flags) {
|
||||||
|
return SSL_write(ssl, buf.ptr, cast(uint) buf.length);
|
||||||
|
}
|
||||||
|
override ptrdiff_t send(const(void)[] buf) {
|
||||||
|
return send(buf, SocketFlags.NONE);
|
||||||
|
}
|
||||||
|
@trusted
|
||||||
|
override ptrdiff_t receive(void[] buf, SocketFlags flags) {
|
||||||
|
return SSL_read(ssl, buf.ptr, cast(int)buf.length);
|
||||||
|
}
|
||||||
|
override ptrdiff_t receive(void[] buf) {
|
||||||
|
return receive(buf, SocketFlags.NONE);
|
||||||
|
}
|
||||||
|
|
||||||
|
this(AddressFamily af, SocketType type = SocketType.STREAM) {
|
||||||
|
super(af, type);
|
||||||
|
initSsl();
|
||||||
|
}
|
||||||
|
|
||||||
|
this(socket_t sock, AddressFamily af) {
|
||||||
|
super(sock, af);
|
||||||
|
initSsl();
|
||||||
|
}
|
||||||
|
|
||||||
|
~this() {
|
||||||
|
SSL_free(ssl);
|
||||||
|
SSL_CTX_free(ctx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue