GTMHTTPServer.m 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630
  1. /* Copyright 2010 Google Inc.
  2. *
  3. * Licensed under the Apache License, Version 2.0 (the "License");
  4. * you may not use this file except in compliance with the License.
  5. * You may obtain a copy of the License at
  6. *
  7. * http://www.apache.org/licenses/LICENSE-2.0
  8. *
  9. * Unless required by applicable law or agreed to in writing, software
  10. * distributed under the License is distributed on an "AS IS" BASIS,
  11. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. * See the License for the specific language governing permissions and
  13. * limitations under the License.
  14. */
  15. //
  16. // Based a little on HTTPServer, part of the CocoaHTTPServer sample code found at
  17. // https://opensource.apple.com/source/HTTPServer/HTTPServer-11/CocoaHTTPServer/
  18. // License for the CocoaHTTPServer sample code:
  19. //
  20. // Software License Agreement (BSD License)
  21. //
  22. // Copyright (c) 2011, Deusty, LLC
  23. // All rights reserved.
  24. //
  25. // Redistribution and use of this software in source and binary forms,
  26. // with or without modification, are permitted provided that the following conditions are met:
  27. //
  28. // * Redistributions of source code must retain the above
  29. // copyright notice, this list of conditions and the
  30. // following disclaimer.
  31. //
  32. // * Neither the name of Deusty nor the names of its
  33. // contributors may be used to endorse or promote products
  34. // derived from this software without specific prior
  35. // written permission of Deusty, LLC.
  36. //
  37. // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
  38. // IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
  39. // FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
  40. // CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
  41. // CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
  42. // SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
  43. // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
  44. // OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
  45. // POSSIBILITY OF SUCH DAMAGE.
  46. //
  47. #import <netinet/in.h>
  48. #import <sys/socket.h>
  49. #import <unistd.h>
  50. #define GTMHTTPSERVER_DEFINE_GLOBALS
  51. #import "GTMHTTPServer.h"
  52. // avoid some of GTM's promiscuous dependencies
  53. #ifndef _GTMDevLog
  54. #define _GTMDevLog NSLog
  55. #endif
  56. #ifndef GTM_STATIC_CAST
  57. #define GTM_STATIC_CAST(type, object) ((type *)(object))
  58. #endif
  59. #ifndef GTMCFAutorelease
  60. #define GTMCFAutorelease(x) ([(id)x autorelease])
  61. #endif
  62. @interface GTMHTTPServer (PrivateMethods)
  63. - (void)acceptedConnectionNotification:(NSNotification *)notification;
  64. - (NSMutableDictionary *)connectionWithFileHandle:(NSFileHandle *)fileHandle;
  65. - (void)dataAvailableNotification:(NSNotification *)notification;
  66. - (NSMutableDictionary *)lookupConnection:(NSFileHandle *)fileHandle;
  67. - (void)closeConnection:(NSMutableDictionary *)connDict;
  68. - (void)sendResponseOnNewThread:(NSMutableDictionary *)connDict;
  69. - (void)sentResponse:(NSMutableDictionary *)connDict;
  70. @end
  71. // keys for our connection dictionaries
  72. static NSString *kFileHandle = @"FileHandle";
  73. static NSString *kRequest = @"Request";
  74. static NSString *kResponse = @"Response";
  75. @interface GTMHTTPRequestMessage (PrivateHelpers)
  76. - (BOOL)isHeaderComplete;
  77. - (BOOL)appendData:(NSData *)data;
  78. - (NSString *)headerFieldValueForKey:(NSString *)key;
  79. - (UInt32)contentLength;
  80. - (void)setBody:(NSData *)body;
  81. @end
  82. @interface GTMHTTPResponseMessage ()
  83. - (id)initWithBody:(NSData *)body contentType:(NSString *)contentType statusCode:(int)statusCode;
  84. - (NSData *)serializedData;
  85. @end
  86. @implementation GTMHTTPServer
  87. - (id)init {
  88. return [self initWithDelegate:nil];
  89. }
  90. - (id)initWithDelegate:(id)delegate {
  91. self = [super init];
  92. if (self) {
  93. if (!delegate) {
  94. _GTMDevLog(@"missing delegate");
  95. [self release];
  96. return nil;
  97. }
  98. delegate_ = delegate;
  99. #ifndef NS_BLOCK_ASSERTIONS
  100. BOOL isDelegateOK = [delegate_ respondsToSelector:@selector(httpServer:handleRequest:)];
  101. NSAssert(isDelegateOK, @"GTMHTTPServer delegate lacks handleRequest sel");
  102. #endif
  103. localhostOnly_ = YES;
  104. connections_ = [[NSMutableArray alloc] init];
  105. }
  106. return self;
  107. }
  108. - (void)dealloc {
  109. [self stop];
  110. [connections_ release];
  111. [super dealloc];
  112. }
  113. #if !TARGET_OS_IPHONE
  114. - (void)finalize {
  115. [self stop];
  116. [super finalize];
  117. }
  118. #endif
  119. - (id)delegate {
  120. return delegate_;
  121. }
  122. - (uint16_t)port {
  123. return port_;
  124. }
  125. - (void)setPort:(uint16_t)port {
  126. port_ = port;
  127. }
  128. - (BOOL)reusePort {
  129. return reusePort_;
  130. }
  131. - (void)setReusePort:(BOOL)yesno {
  132. reusePort_ = yesno;
  133. }
  134. - (BOOL)localhostOnly {
  135. return localhostOnly_;
  136. }
  137. - (void)setLocalhostOnly:(BOOL)yesno {
  138. localhostOnly_ = yesno;
  139. }
  140. - (BOOL)start:(NSError **)error {
  141. NSAssert(listenHandle_ == nil, @"start called when we already have a listenHandle_");
  142. if (error) *error = NULL;
  143. NSInteger startFailureCode = 0;
  144. int fd = socket(AF_INET, SOCK_STREAM, 0);
  145. if (fd <= 0) {
  146. // COV_NF_START - we'd need to use up *all* sockets to test this?
  147. startFailureCode = kGTMHTTPServerSocketCreateFailedError;
  148. goto startFailed;
  149. // COV_NF_END
  150. }
  151. // enable address reuse quicker after we are done w/ our socket
  152. int yes = 1;
  153. int sock_opt = reusePort_ ? SO_REUSEPORT : SO_REUSEADDR;
  154. if (setsockopt(fd, SOL_SOCKET, sock_opt, (void *)&yes, (socklen_t)sizeof(yes)) != 0) {
  155. _GTMDevLog(@"failed to mark the socket as reusable"); // COV_NF_LINE
  156. }
  157. // bind
  158. struct sockaddr_in addr;
  159. bzero(&addr, sizeof(addr));
  160. addr.sin_len = sizeof(addr);
  161. addr.sin_family = AF_INET;
  162. addr.sin_port = htons(port_);
  163. if (localhostOnly_) {
  164. addr.sin_addr.s_addr = htonl(0x7F000001);
  165. } else {
  166. // COV_NF_START - testing this could cause a leopard firewall prompt during tests.
  167. addr.sin_addr.s_addr = htonl(INADDR_ANY);
  168. // COV_NF_END
  169. }
  170. if (bind(fd, (struct sockaddr *)(&addr), (socklen_t)sizeof(addr)) != 0) {
  171. startFailureCode = kGTMHTTPServerBindFailedError;
  172. goto startFailed;
  173. }
  174. // collect the port back out
  175. if (port_ == 0) {
  176. socklen_t len = (socklen_t)sizeof(addr);
  177. if (getsockname(fd, (struct sockaddr *)(&addr), &len) == 0) {
  178. port_ = ntohs(addr.sin_port);
  179. }
  180. }
  181. // tell it to listen for connections
  182. if (listen(fd, 5) != 0) {
  183. // COV_NF_START
  184. startFailureCode = kGTMHTTPServerListenFailedError;
  185. goto startFailed;
  186. // COV_NF_END
  187. }
  188. // now use a filehandle to accept connections
  189. listenHandle_ = [[NSFileHandle alloc] initWithFileDescriptor:fd closeOnDealloc:YES];
  190. if (listenHandle_ == nil) {
  191. // COV_NF_START - we'd need to run out of memory to test this?
  192. startFailureCode = kGTMHTTPServerHandleCreateFailedError;
  193. goto startFailed;
  194. // COV_NF_END
  195. }
  196. // setup notifications for connects
  197. NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
  198. [center addObserver:self
  199. selector:@selector(acceptedConnectionNotification:)
  200. name:NSFileHandleConnectionAcceptedNotification
  201. object:listenHandle_];
  202. [listenHandle_ acceptConnectionInBackgroundAndNotify];
  203. // TODO: maybe hit the delegate incase it wants to register w/ NSNetService,
  204. // or just know we're up and running?
  205. return YES;
  206. startFailed:
  207. if (error) {
  208. *error = [[[NSError alloc] initWithDomain:kGTMHTTPServerErrorDomain
  209. code:startFailureCode
  210. userInfo:nil] autorelease];
  211. }
  212. if (fd > 0) {
  213. close(fd);
  214. }
  215. return NO;
  216. }
  217. - (void)stop {
  218. if (listenHandle_) {
  219. NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
  220. [center removeObserver:self
  221. name:NSFileHandleConnectionAcceptedNotification
  222. object:listenHandle_];
  223. [listenHandle_ release];
  224. listenHandle_ = nil;
  225. // TODO: maybe hit the delegate in case it wants to unregister w/
  226. // NSNetService, or just know we've stopped running?
  227. }
  228. [connections_ removeAllObjects];
  229. }
  230. - (NSUInteger)activeRequestCount {
  231. return [connections_ count];
  232. }
  233. - (NSString *)description {
  234. NSString *result =
  235. [NSString stringWithFormat:@"%@<%p>{ port=%d localHostOnly=%@ status=%@ }", [self class],
  236. self, port_, (localhostOnly_ ? @"YES" : @"NO"),
  237. (listenHandle_ != nil ? @"Started" : @"Stopped")];
  238. return result;
  239. }
  240. @end
  241. @implementation GTMHTTPServer (PrivateMethods)
  242. - (void)acceptedConnectionNotification:(NSNotification *)notification {
  243. NSDictionary *userInfo = [notification userInfo];
  244. NSFileHandle *newConnection = [userInfo objectForKey:NSFileHandleNotificationFileHandleItem];
  245. NSAssert1(newConnection != nil, @"failed to get the connection in the notification: %@",
  246. notification);
  247. // make sure we accept more...
  248. [listenHandle_ acceptConnectionInBackgroundAndNotify];
  249. // TODO: could let the delegate look at the address, before we start working
  250. // on it.
  251. NSMutableDictionary *connDict = [self connectionWithFileHandle:newConnection];
  252. [connections_ addObject:connDict];
  253. }
  254. - (NSMutableDictionary *)connectionWithFileHandle:(NSFileHandle *)fileHandle {
  255. NSMutableDictionary *result = [NSMutableDictionary dictionary];
  256. [result setObject:fileHandle forKey:kFileHandle];
  257. GTMHTTPRequestMessage *request = [[[GTMHTTPRequestMessage alloc] init] autorelease];
  258. [result setObject:request forKey:kRequest];
  259. // setup for data notifications
  260. NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
  261. [center addObserver:self
  262. selector:@selector(dataAvailableNotification:)
  263. name:NSFileHandleReadCompletionNotification
  264. object:fileHandle];
  265. [fileHandle readInBackgroundAndNotify];
  266. return result;
  267. }
  268. - (void)dataAvailableNotification:(NSNotification *)notification {
  269. NSFileHandle *connectionHandle = GTM_STATIC_CAST(NSFileHandle, [notification object]);
  270. NSMutableDictionary *connDict = [self lookupConnection:connectionHandle];
  271. if (connDict == nil) return; // we are no longer tracking this one
  272. NSDictionary *userInfo = [notification userInfo];
  273. NSData *readData = [userInfo objectForKey:NSFileHandleNotificationDataItem];
  274. if ([readData length] == 0) {
  275. // remote side closed
  276. [self closeConnection:connDict];
  277. return;
  278. }
  279. // Use a local pool to keep memory down incase the runloop we're in doesn't
  280. // drain until it gets a UI event.
  281. NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
  282. @try {
  283. // Like Apple's sample, we just keep adding data until we get a full header
  284. // and any referenced body.
  285. GTMHTTPRequestMessage *request = [connDict objectForKey:kRequest];
  286. [request appendData:readData];
  287. // Is the header complete yet?
  288. if (![request isHeaderComplete]) {
  289. // more data...
  290. [connectionHandle readInBackgroundAndNotify];
  291. } else {
  292. // Do we have all the body?
  293. UInt32 contentLength = [request contentLength];
  294. NSData *body = [request body];
  295. NSUInteger bodyLength = [body length];
  296. if (contentLength > bodyLength) {
  297. // need more data...
  298. [connectionHandle readInBackgroundAndNotify];
  299. } else {
  300. if (contentLength < bodyLength) {
  301. // We got extra (probably someone trying to pipeline on us), trim
  302. // and let the extra data go...
  303. NSData *newBody = [NSData dataWithBytes:[body bytes] length:contentLength];
  304. [request setBody:newBody];
  305. _GTMDevLog(@"Got %lu extra bytes on http request, ignoring them",
  306. (unsigned long)(bodyLength - contentLength));
  307. }
  308. GTMHTTPResponseMessage *response = nil;
  309. @try {
  310. // Off to the delegate
  311. response = [delegate_ httpServer:self handleRequest:request];
  312. } @catch (NSException *e) {
  313. _GTMDevLog(@"Exception trying to handle http request: %@", e);
  314. } // COV_NF_LINE - radar 5851992 only reachable w/ an uncaught exception which isn't
  315. // testable
  316. if (response) {
  317. // We don't support connection reuse, so we add (force) the header to
  318. // close every connection.
  319. [response setValue:@"close" forHeaderField:@"Connection"];
  320. // spawn thread to send reply (since we do a blocking send)
  321. [connDict setObject:response forKey:kResponse];
  322. [NSThread detachNewThreadSelector:@selector(sendResponseOnNewThread:)
  323. toTarget:self
  324. withObject:connDict];
  325. } else {
  326. // No response, shut it down
  327. [self closeConnection:connDict];
  328. }
  329. }
  330. }
  331. } @catch (NSException *e) { // COV_NF_START
  332. _GTMDevLog(@"exception while read data: %@", e);
  333. // exception while dealing with the connection, close it
  334. } // COV_NF_END
  335. @finally {
  336. [pool drain];
  337. }
  338. }
  339. - (NSMutableDictionary *)lookupConnection:(NSFileHandle *)fileHandle {
  340. NSMutableDictionary *result = nil;
  341. for (NSMutableDictionary *connDict in connections_) {
  342. if (fileHandle == [connDict objectForKey:kFileHandle]) {
  343. result = connDict;
  344. break;
  345. }
  346. }
  347. return result;
  348. }
  349. - (void)closeConnection:(NSMutableDictionary *)connDict {
  350. // remove the notification
  351. NSFileHandle *connectionHandle = [connDict objectForKey:kFileHandle];
  352. NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
  353. [center removeObserver:self name:NSFileHandleReadCompletionNotification object:connectionHandle];
  354. // in a non GC world, we're fine just letting the connect get closed when
  355. // the object is release when it comes out of connections_, but in a GC world
  356. // it won't get cleaned up
  357. [connectionHandle closeFile];
  358. // remove it from the list
  359. [connections_ removeObject:connDict];
  360. }
  361. - (void)sendResponseOnNewThread:(NSMutableDictionary *)connDict {
  362. NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
  363. @try {
  364. GTMHTTPResponseMessage *response = [connDict objectForKey:kResponse];
  365. NSFileHandle *connectionHandle = [connDict objectForKey:kFileHandle];
  366. NSData *serialized = [response serializedData];
  367. [connectionHandle writeData:serialized];
  368. } @catch (NSException *e) { // COV_NF_START - causing an exception here is to hard in a test
  369. // TODO: let the delegate know about the exception (but do it on the main
  370. // thread)
  371. _GTMDevLog(@"exception while sending reply: %@", e);
  372. } // COV_NF_END
  373. // back to the main thread to close things down
  374. [self performSelectorOnMainThread:@selector(sentResponse:) withObject:connDict waitUntilDone:NO];
  375. [pool release];
  376. }
  377. - (void)sentResponse:(NSMutableDictionary *)connDict {
  378. // make sure we're still tracking this connection (in case server was stopped)
  379. NSFileHandle *connection = [connDict objectForKey:kFileHandle];
  380. NSMutableDictionary *connDict2 = [self lookupConnection:connection];
  381. if (connDict != connDict2) return;
  382. // TODO: message the delegate that it was sent
  383. // close it down
  384. [self closeConnection:connDict];
  385. }
  386. @end
  387. #pragma mark -
  388. @implementation GTMHTTPRequestMessage
  389. - (id)init {
  390. self = [super init];
  391. if (self) {
  392. message_ = CFHTTPMessageCreateEmpty(kCFAllocatorDefault, YES);
  393. }
  394. return self;
  395. }
  396. - (void)dealloc {
  397. if (message_) {
  398. CFRelease(message_);
  399. }
  400. [super dealloc];
  401. }
  402. - (NSString *)version {
  403. return GTMCFAutorelease(CFHTTPMessageCopyVersion(message_));
  404. }
  405. - (NSURL *)URL {
  406. return GTMCFAutorelease(CFHTTPMessageCopyRequestURL(message_));
  407. }
  408. - (NSString *)method {
  409. return GTMCFAutorelease(CFHTTPMessageCopyRequestMethod(message_));
  410. }
  411. - (NSData *)body {
  412. return GTMCFAutorelease(CFHTTPMessageCopyBody(message_));
  413. }
  414. - (NSDictionary *)allHeaderFieldValues {
  415. return GTMCFAutorelease(CFHTTPMessageCopyAllHeaderFields(message_));
  416. }
  417. - (NSString *)description {
  418. CFStringRef desc = CFCopyDescription(message_);
  419. NSString *result = [NSString stringWithFormat:@"%@<%p>{ message=%@ }", [self class], self, desc];
  420. CFRelease(desc);
  421. return result;
  422. }
  423. @end
  424. @implementation GTMHTTPRequestMessage (PrivateHelpers)
  425. - (BOOL)isHeaderComplete {
  426. return CFHTTPMessageIsHeaderComplete(message_) ? YES : NO;
  427. }
  428. - (BOOL)appendData:(NSData *)data {
  429. return CFHTTPMessageAppendBytes(message_, [data bytes], (CFIndex)[data length]) ? YES : NO;
  430. }
  431. - (NSString *)headerFieldValueForKey:(NSString *)key {
  432. CFStringRef value = NULL;
  433. if (key) {
  434. value = CFHTTPMessageCopyHeaderFieldValue(message_, (CFStringRef)key);
  435. }
  436. return GTMCFAutorelease(value);
  437. }
  438. - (UInt32)contentLength {
  439. return (UInt32)[[self headerFieldValueForKey:@"Content-Length"] intValue];
  440. }
  441. - (void)setBody:(NSData *)body {
  442. if (!body) {
  443. body = [NSData data]; // COV_NF_LINE - can only happen in we fail to make the new data object
  444. }
  445. CFHTTPMessageSetBody(message_, (CFDataRef)body);
  446. }
  447. @end
  448. #pragma mark -
  449. @implementation GTMHTTPResponseMessage
  450. - (id)init {
  451. return [self initWithBody:nil contentType:nil statusCode:0];
  452. }
  453. - (id)initWithBody:(NSData *)body contentType:(NSString *)contentType statusCode:(int)statusCode {
  454. self = [super init];
  455. if (self) {
  456. if ((statusCode < 100) || (statusCode > 599)) {
  457. [self release];
  458. return nil;
  459. }
  460. message_ =
  461. CFHTTPMessageCreateResponse(kCFAllocatorDefault, statusCode, NULL, kCFHTTPVersion1_0);
  462. if (!message_) {
  463. // COV_NF_START
  464. [self release];
  465. return nil;
  466. // COV_NF_END
  467. }
  468. NSUInteger bodyLength = 0;
  469. if (body) {
  470. bodyLength = [body length];
  471. CFHTTPMessageSetBody(message_, (CFDataRef)body);
  472. }
  473. if ([contentType length] == 0) {
  474. contentType = @"text/html";
  475. }
  476. NSString *bodyLenStr = [NSString stringWithFormat:@"%lu", (unsigned long)bodyLength];
  477. [self setValue:bodyLenStr forHeaderField:@"Content-Length"];
  478. [self setValue:contentType forHeaderField:@"Content-Type"];
  479. }
  480. return self;
  481. }
  482. - (void)dealloc {
  483. if (message_) {
  484. CFRelease(message_);
  485. }
  486. [super dealloc];
  487. }
  488. + (instancetype)responseWithString:(NSString *)plainText {
  489. NSData *body = [plainText dataUsingEncoding:NSUTF8StringEncoding];
  490. return [self responseWithBody:body contentType:@"text/plain; charset=UTF-8" statusCode:200];
  491. }
  492. + (instancetype)responseWithHTMLString:(NSString *)htmlString {
  493. return [self responseWithBody:[htmlString dataUsingEncoding:NSUTF8StringEncoding]
  494. contentType:@"text/html; charset=UTF-8"
  495. statusCode:200];
  496. }
  497. + (instancetype)responseWithBody:(NSData *)body
  498. contentType:(NSString *)contentType
  499. statusCode:(int)statusCode {
  500. return [[[[self class] alloc] initWithBody:body contentType:contentType statusCode:statusCode]
  501. autorelease];
  502. }
  503. + (instancetype)emptyResponseWithCode:(int)statusCode {
  504. return
  505. [[[[self class] alloc] initWithBody:nil contentType:nil statusCode:statusCode] autorelease];
  506. }
  507. - (void)setValue:(NSString *)value forHeaderField:(NSString *)headerField {
  508. if ([headerField length] == 0) return;
  509. if (value == nil) {
  510. value = @"";
  511. }
  512. CFHTTPMessageSetHeaderFieldValue(message_, (CFStringRef)headerField, (CFStringRef)value);
  513. }
  514. - (void)setHeaderValuesFromDictionary:(NSDictionary *)dict {
  515. for (id key in dict) {
  516. id value = [dict valueForKey:key];
  517. [self setValue:value forHeaderField:key];
  518. }
  519. }
  520. - (NSString *)description {
  521. CFStringRef desc = CFCopyDescription(message_);
  522. NSString *result = [NSString stringWithFormat:@"%@<%p>{ message=%@ }", [self class], self, desc];
  523. CFRelease(desc);
  524. return result;
  525. }
  526. - (NSData *)serializedData {
  527. return GTMCFAutorelease(CFHTTPMessageCopySerializedMessage(message_));
  528. }
  529. @end