diff options
-rw-r--r-- | Foundation/GTMHTTPServer.h | 11 | ||||
-rw-r--r-- | Foundation/GTMHTTPServer.m | 18 | ||||
-rw-r--r-- | Foundation/GTMHTTPServerTest.m | 44 |
3 files changed, 70 insertions, 3 deletions
diff --git a/Foundation/GTMHTTPServer.h b/Foundation/GTMHTTPServer.h index 0caa149..3920968 100644 --- a/Foundation/GTMHTTPServer.h +++ b/Foundation/GTMHTTPServer.h @@ -64,13 +64,14 @@ enum { @private __weak id delegate_; uint16_t port_; + BOOL reusePort_; BOOL localhostOnly_; NSFileHandle *listenHandle_; NSMutableArray *connections_; } // The delegate must support the httpServer:handleRequest: method in -// NSObject(GTMHTTPServerDeletateMethods) below. +// NSObject(GTMHTTPServerDelegateMethods) below. - (id)initWithDelegate:(id)delegate; - (id)delegate; @@ -79,6 +80,11 @@ enum { - (uint16_t)port; - (void)setPort:(uint16_t)port; +// Controls listening socket behavior: SO_REUSEADDR vs SO_REUSEPORT. +// The default is NO (SO_REUSEADDR) +- (BOOL)reusePort; +- (void)setReusePort:(BOOL)reusePort; + // Receive connections on the localHost loopback address only or on all // interfaces for this machine. The default is to only listen on localhost. - (BOOL)localhostOnly; @@ -98,7 +104,7 @@ enum { @end -@interface NSObject (GTMHTTPServerDeletateMethods) +@interface NSObject (GTMHTTPServerDelegateMethods) - (GTMHTTPResponseMessage *)httpServer:(GTMHTTPServer *)server handleRequest:(GTMHTTPRequestMessage *)request; @end @@ -126,6 +132,7 @@ enum { @private CFHTTPMessageRef message_; } ++ (id)responseWithString:(NSString *)plainText; + (id)responseWithHTMLString:(NSString *)htmlString; + (id)responseWithBody:(NSData *)body contentType:(NSString *)contentType diff --git a/Foundation/GTMHTTPServer.m b/Foundation/GTMHTTPServer.m index a514842..57e6077 100644 --- a/Foundation/GTMHTTPServer.m +++ b/Foundation/GTMHTTPServer.m @@ -114,6 +114,14 @@ static NSString *kResponse = @"Response"; port_ = port; } +- (BOOL)reusePort { + return reusePort_; +} + +- (void)setReusePort:(BOOL)yesno { + reusePort_ = yesno; +} + - (BOOL)localhostOnly { return localhostOnly_; } @@ -139,7 +147,8 @@ static NSString *kResponse = @"Response"; // enable address reuse quicker after we are done w/ our socket int yes = 1; - if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, + int sock_opt = reusePort_ ? SO_REUSEPORT : SO_REUSEADDR; + if (setsockopt(fd, SOL_SOCKET, sock_opt, (void *)&yes, (socklen_t)sizeof(yes)) != 0) { _GTMDevLog(@"failed to mark the socket as reusable"); // COV_NF_LINE } @@ -526,6 +535,13 @@ startFailed: [super dealloc]; } ++ (id)responseWithString:(NSString *)plainText { + NSData *body = [plainText dataUsingEncoding:NSUTF8StringEncoding]; + return [self responseWithBody:body + contentType:@"text/plain; charset=UTF-8" + statusCode:200]; +} + + (id)responseWithHTMLString:(NSString *)htmlString { return [self responseWithBody:[htmlString dataUsingEncoding:NSUTF8StringEncoding] contentType:@"text/html; charset=UTF-8" diff --git a/Foundation/GTMHTTPServerTest.m b/Foundation/GTMHTTPServerTest.m index 11ce8f2..0cafaa2 100644 --- a/Foundation/GTMHTTPServerTest.m +++ b/Foundation/GTMHTTPServerTest.m @@ -150,6 +150,28 @@ const NSTimeInterval kSendChunkInterval = 0.05; [server2 stop]; } +- (void)testRestart { + TestServerDelegate *delegate = [TestServerDelegate testServerDelegate]; + STAssertNotNil(delegate, nil); + GTMHTTPServer *server = + [[[GTMHTTPServer alloc] initWithDelegate:delegate] autorelease]; + STAssertNotNil(server, nil); + [server setLocalhostOnly:YES]; + [server setReusePort:YES]; + NSError *error = nil; + STAssertTrue([server start:&error], @"failed to start (error=%@)", error); + STAssertNil(error, @"error: %@", error); + uint16_t prevPort = [server port]; + STAssertGreaterThanOrEqual(prevPort, (uint16_t)1024, + @"how'd we get a reserved port?"); + // restart and make sure it works + [server stop]; + error = nil; + [server setPort:prevPort]; + STAssertTrue([server start:&error], @"failed to start (error=%@)", error); + STAssertNil(error, @"error: %@", error); +} + - (void)testRequests { TestServerDelegate *delegate = [TestServerDelegate testServerDelegate]; STAssertNotNil(delegate, nil); @@ -328,6 +350,28 @@ const NSTimeInterval kSendChunkInterval = 0.05; STAssertNotEquals([responseString rangeOfString:@"Custom-Header2: "].location, (NSUInteger)NSNotFound, @"String: %@", responseString); + // test plain text response + + expectedResponse = [GTMHTTPResponseMessage responseWithString:@"BAR"]; + STAssertNotNil(expectedResponse, nil); + STAssertGreaterThan([[expectedResponse description] length], + (NSUInteger)3, nil); + [delegate pushResponse:expectedResponse]; + responseData = [self fetchFromPort:[server port] + payload:@"GET /bar HTTP/1.0\r\n\r\n" + chunkSize:kSendChunkSize]; + STAssertNotNil(responseData, nil); + responseString = + [[[NSString alloc] initWithData:responseData + encoding:NSUTF8StringEncoding] autorelease]; + STAssertNotNil(responseString, nil); + STAssertTrue([responseString hasPrefix:@"HTTP/1.0 200 "], nil); + STAssertTrue([responseString hasSuffix:@"BAR"], @"should end w/ our data"); + STAssertNotEquals([responseString rangeOfString:@"Content-Length: 3"].location, + (NSUInteger)NSNotFound, nil); + STAssertNotEquals([responseString rangeOfString:@"Content-Type: text/plain"].location, + (NSUInteger)NSNotFound, nil); + [server stop]; } |