Index: src/OFHTTPServer.h ================================================================== --- src/OFHTTPServer.h +++ src/OFHTTPServer.h @@ -21,13 +21,14 @@ # error No sockets available! #endif OF_ASSUME_NONNULL_BEGIN -@class OFHTTPServer; @class OFHTTPRequest; @class OFHTTPResponse; +@class OFHTTPServer; +@class OFStream; @class OFTCPSocket; /*! * @protocol OFHTTPServerDelegate OFHTTPServer.h ObjFW/OFHTTPServer.h * @@ -38,14 +39,16 @@ * @brief This method is called when the HTTP server received a request from a * client. * * @param server The HTTP server which received the request * @param request The request the HTTP server received + * @param body A stream to read the body of the request from, if any * @param response The response the server will send to the client */ - (void)server: (OFHTTPServer *)server didReceiveRequest: (OFHTTPRequest *)request + body: (nullable OFStream *)body response: (OFHTTPResponse *)response; @optional /*! * @brief This method is called when the HTTP server's listening socket Index: src/OFHTTPServer.m ================================================================== --- src/OFHTTPServer.m +++ src/OFHTTPServer.m @@ -52,10 +52,66 @@ - (bool)of_socket: (OFTCPSocket *)sock didAcceptSocket: (OFTCPSocket *)clientSocket context: (id)context exception: (id)exception; @end + +@interface OFHTTPServerResponse: OFHTTPResponse +{ + OFTCPSocket *_socket; + OFHTTPServer *_server; + OFHTTPRequest *_request; + bool _chunked, _headersSent; +} + +- (instancetype)initWithSocket: (OFTCPSocket *)sock + server: (OFHTTPServer *)server + request: (OFHTTPRequest *)request; +@end + +@interface OFHTTPServer_Connection: OFObject +{ +@public + OFTCPSocket *_socket; + OFHTTPServer *_server; + OFTimer *_timer; + enum { + AWAITING_PROLOG, + PARSING_HEADERS, + SEND_RESPONSE + } _state; + uint8_t _HTTPMinorVersion; + of_http_request_method_t _method; + OFString *_host, *_path; + uint16_t _port; + OFMutableDictionary *_headers; + size_t _contentLength; + OFStream *_body; +} + +- (instancetype)initWithSocket: (OFTCPSocket *)sock + server: (OFHTTPServer *)server; +- (bool)socket: (OFTCPSocket *)sock + didReadLine: (OFString *)line + context: (id)context + exception: (id)exception; +- (bool)parseProlog: (OFString *)line; +- (bool)parseHeaders: (OFString *)line; +- (bool)sendErrorAndClose: (short)statusCode; +- (void)createResponse; +@end + +@interface OFHTTPServerRequestBodyStream: OFStream +{ + OFTCPSocket *_socket; + uintmax_t _toRead; + bool _atEndOfStream; +} + +- (instancetype)initWithSocket: (OFTCPSocket *)sock + contentLength: (uintmax_t)contentLength; +@end static const char * statusCodeToString(short code) { switch (code) { @@ -172,23 +228,10 @@ return [OFString stringWithUTF8StringNoCopy: cString freeWhenDone: true]; } -@interface OFHTTPServerResponse: OFHTTPResponse -{ - OFTCPSocket *_socket; - OFHTTPServer *_server; - OFHTTPRequest *_request; - bool _chunked, _headersSent; -} - -- (instancetype)initWithSocket: (OFTCPSocket *)sock - server: (OFHTTPServer *)server - request: (OFHTTPRequest *)request; -@end - @implementation OFHTTPServerResponse - (instancetype)initWithSocket: (OFTCPSocket *)sock server: (OFHTTPServer *)server request: (OFHTTPRequest *)request { @@ -322,46 +365,10 @@ return [_socket fileDescriptorForWriting]; } @end -@interface OFHTTPServer_Connection: OFObject -{ - OFTCPSocket *_socket; - OFHTTPServer *_server; - OFTimer *_timer; - enum { - AWAITING_PROLOG, - PARSING_HEADERS, - SEND_RESPONSE - } _state; - uint8_t _HTTPMinorVersion; - of_http_request_method_t _method; - OFString *_host, *_path; - uint16_t _port; - OFMutableDictionary *_headers; - size_t _contentLength; - OFMutableData *_body; -} - -- (instancetype)initWithSocket: (OFTCPSocket *)sock - server: (OFHTTPServer *)server; -- (bool)socket: (OFTCPSocket *)sock - didReadLine: (OFString *)line - context: (id)context - exception: (id)exception; -- (bool)parseProlog: (OFString *)line; -- (bool)parseHeaders: (OFString *)line; -- (bool)socket: (OFTCPSocket *)sock - didReadIntoBuffer: (char *)buffer - length: (size_t)length - context: (id)context - exception: (id)exception; -- (bool)sendErrorAndClose: (short)statusCode; -- (void)createResponse; -@end - @implementation OFHTTPServer_Connection - (instancetype)initWithSocket: (OFTCPSocket *)sock server: (OFHTTPServer *)server { self = [super init]; @@ -411,19 +418,11 @@ @try { switch (_state) { case AWAITING_PROLOG: return [self parseProlog: line]; case PARSING_HEADERS: - if (![self parseHeaders: line]) - return false; - - if (_state == SEND_RESPONSE) { - [self createResponse]; - return false; - } - - return true; + return [self parseHeaders: line]; default: return false; } } @catch (OFWriteFailedException *e) { return false; @@ -493,46 +492,42 @@ { OFString *key, *value, *old; size_t pos; if ([line length] == 0) { - intmax_t contentLength; - - @try { - contentLength = [[_headers - objectForKey: @"Content-Length"] decimalValue]; - } @catch (OFInvalidFormatException *e) { - return [self sendErrorAndClose: 400]; - } - - if (contentLength > 0) { - char *buffer; - - if (contentLength < 0 || - (uintmax_t)contentLength > SIZE_MAX) - @throw [OFOutOfRangeException exception]; - - buffer = [self allocMemoryWithSize: BUFFER_SIZE]; - _body = [[OFMutableData alloc] init]; - _contentLength = (size_t)contentLength; - - [_socket asyncReadIntoBuffer: buffer - length: BUFFER_SIZE - target: self - selector: @selector(socket: - didReadIntoBuffer: - length:context: - exception:) - context: nil]; - [_timer setFireDate: - [OFDate dateWithTimeIntervalSinceNow: 5]]; - - return false; + OFString *contentLengthString; + + if ((contentLengthString = + [_headers objectForKey: @"Content-Length"]) != nil) { + intmax_t contentLength; + + @try { + contentLength = + [contentLengthString decimalValue]; + + } @catch (OFInvalidFormatException *e) { + return [self sendErrorAndClose: 400]; + } + + if (contentLength < 0) + return [self sendErrorAndClose: 400]; + + [_body release]; + _body = nil; + _body = [[OFHTTPServerRequestBodyStream alloc] + initWithSocket: _socket + contentLength: contentLength]; + + [_timer invalidate]; + [_timer release]; + _timer = nil; } _state = SEND_RESPONSE; - return true; + [self createResponse]; + + return false; } pos = [line rangeOfString: @":"].location; if (pos == OF_NOT_FOUND) return [self sendErrorAndClose: 400]; @@ -582,47 +577,10 @@ } return true; } -- (bool)socket: (OFTCPSocket *)sock - didReadIntoBuffer: (char *)buffer - length: (size_t)length - context: (id)context - exception: (id)exception -{ - if ([sock isAtEndOfStream] || exception != nil) - return false; - - [_body addItems: buffer - count: length]; - - if ([_body count] >= _contentLength) { - /* - * Manually free the buffer here. While this is not required - * now as the async read is the only thing referencing self and - * the buffer is allocated on self, it is required once - * Connection: keep-alive is implemented. - */ - [self freeMemory: buffer]; - - [_body makeImmutable]; - - @try { - [self createResponse]; - } @catch (OFWriteFailedException *e) { - return false; - } - - return false; - } - - [_timer setFireDate: [OFDate dateWithTimeIntervalSinceNow: 5]]; - - return true; -} - - (bool)sendErrorAndClose: (short)statusCode { OFString *date = [[OFDate date] dateStringWithFormat: @"%a, %d %b %Y %H:%M:%S GMT"]; @@ -636,10 +594,11 @@ return false; } - (void)createResponse { + void *pool = objc_autoreleasePoolPush(); OFMutableURL *URL; OFHTTPRequest *request; OFHTTPServerResponse *response; size_t pos; @@ -681,21 +640,90 @@ request = [OFHTTPRequest requestWithURL: URL]; [request setMethod: _method]; [request setProtocolVersion: (of_http_request_protocol_version_t){ 1, _HTTPMinorVersion }]; [request setHeaders: _headers]; - [request setBody: _body]; [request setRemoteAddress: [_socket remoteAddress]]; response = [[[OFHTTPServerResponse alloc] initWithSocket: _socket server: _server request: request] autorelease]; [[_server delegate] server: _server didReceiveRequest: request + body: _body response: response]; + + objc_autoreleasePoolPop(pool); +} +@end + +@implementation OFHTTPServerRequestBodyStream +- (instancetype)initWithSocket: (OFTCPSocket *)sock + contentLength: (uintmax_t)contentLength +{ + self = [super init]; + + @try { + _socket = [sock retain]; + _toRead = contentLength; + } @catch (id e) { + [self release]; + @throw e; + } + + return self; +} + +- (void)dealloc +{ + [self close]; + + [super dealloc]; +} + +- (bool)lowlevelIsAtEndOfStream +{ + return _atEndOfStream; +} + +- (size_t)lowlevelReadIntoBuffer: (void *)buffer + length: (size_t)length +{ + size_t ret; + + if (_toRead == 0) { + _atEndOfStream = true; + return 0; + } + + if (length > _toRead) + length = (size_t)_toRead; + + ret = [_socket readIntoBuffer: buffer + length: length]; + + _toRead -= ret; + + return ret; +} + +- (bool)hasDataInReadBuffer +{ + return ([super hasDataInReadBuffer] || [_socket hasDataInReadBuffer]); +} + +- (int)fileDescriptorForReading +{ + return [_socket fileDescriptorForReading]; +} + +- (void)close +{ + [_socket release]; + _socket = nil; } @end @implementation OFHTTPServer @synthesize host = _host, port = _port, delegate = _delegate, name = _name;