GTMHTTPServer.m 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623
  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 "GoogleUtilities/Example/Tests/Network/third_party/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. - (id)delegate {
  114. return delegate_;
  115. }
  116. - (uint16_t)port {
  117. return port_;
  118. }
  119. - (void)setPort:(uint16_t)port {
  120. port_ = port;
  121. }
  122. - (BOOL)reusePort {
  123. return reusePort_;
  124. }
  125. - (void)setReusePort:(BOOL)yesno {
  126. reusePort_ = yesno;
  127. }
  128. - (BOOL)localhostOnly {
  129. return localhostOnly_;
  130. }
  131. - (void)setLocalhostOnly:(BOOL)yesno {
  132. localhostOnly_ = yesno;
  133. }
  134. - (BOOL)start:(NSError **)error {
  135. NSAssert(listenHandle_ == nil, @"start called when we already have a listenHandle_");
  136. if (error) *error = NULL;
  137. NSInteger startFailureCode = 0;
  138. int fd = socket(AF_INET, SOCK_STREAM, 0);
  139. if (fd <= 0) {
  140. // COV_NF_START - we'd need to use up *all* sockets to test this?
  141. startFailureCode = kGTMHTTPServerSocketCreateFailedError;
  142. goto startFailed;
  143. // COV_NF_END
  144. }
  145. // enable address reuse quicker after we are done w/ our socket
  146. int yes = 1;
  147. int sock_opt = reusePort_ ? SO_REUSEPORT : SO_REUSEADDR;
  148. if (setsockopt(fd, SOL_SOCKET, sock_opt, (void *)&yes, (socklen_t)sizeof(yes)) != 0) {
  149. _GTMDevLog(@"failed to mark the socket as reusable"); // COV_NF_LINE
  150. }
  151. // bind
  152. struct sockaddr_in addr;
  153. bzero(&addr, sizeof(addr));
  154. addr.sin_len = sizeof(addr);
  155. addr.sin_family = AF_INET;
  156. addr.sin_port = htons(port_);
  157. if (localhostOnly_) {
  158. addr.sin_addr.s_addr = htonl(0x7F000001);
  159. } else {
  160. // COV_NF_START - testing this could cause a leopard firewall prompt during tests.
  161. addr.sin_addr.s_addr = htonl(INADDR_ANY);
  162. // COV_NF_END
  163. }
  164. if (bind(fd, (struct sockaddr *)(&addr), (socklen_t)sizeof(addr)) != 0) {
  165. startFailureCode = kGTMHTTPServerBindFailedError;
  166. goto startFailed;
  167. }
  168. // collect the port back out
  169. if (port_ == 0) {
  170. socklen_t len = (socklen_t)sizeof(addr);
  171. if (getsockname(fd, (struct sockaddr *)(&addr), &len) == 0) {
  172. port_ = ntohs(addr.sin_port);
  173. }
  174. }
  175. // tell it to listen for connections
  176. if (listen(fd, 5) != 0) {
  177. // COV_NF_START
  178. startFailureCode = kGTMHTTPServerListenFailedError;
  179. goto startFailed;
  180. // COV_NF_END
  181. }
  182. // now use a filehandle to accept connections
  183. listenHandle_ = [[NSFileHandle alloc] initWithFileDescriptor:fd closeOnDealloc:YES];
  184. if (listenHandle_ == nil) {
  185. // COV_NF_START - we'd need to run out of memory to test this?
  186. startFailureCode = kGTMHTTPServerHandleCreateFailedError;
  187. goto startFailed;
  188. // COV_NF_END
  189. }
  190. // setup notifications for connects
  191. NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
  192. [center addObserver:self
  193. selector:@selector(acceptedConnectionNotification:)
  194. name:NSFileHandleConnectionAcceptedNotification
  195. object:listenHandle_];
  196. [listenHandle_ acceptConnectionInBackgroundAndNotify];
  197. // TODO: maybe hit the delegate incase it wants to register w/ NSNetService,
  198. // or just know we're up and running?
  199. return YES;
  200. startFailed:
  201. if (error) {
  202. *error = [[[NSError alloc] initWithDomain:kGTMHTTPServerErrorDomain
  203. code:startFailureCode
  204. userInfo:nil] autorelease];
  205. }
  206. if (fd > 0) {
  207. close(fd);
  208. }
  209. return NO;
  210. }
  211. - (void)stop {
  212. if (listenHandle_) {
  213. NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
  214. [center removeObserver:self
  215. name:NSFileHandleConnectionAcceptedNotification
  216. object:listenHandle_];
  217. [listenHandle_ release];
  218. listenHandle_ = nil;
  219. // TODO: maybe hit the delegate in case it wants to unregister w/
  220. // NSNetService, or just know we've stopped running?
  221. }
  222. [connections_ removeAllObjects];
  223. }
  224. - (NSUInteger)activeRequestCount {
  225. return [connections_ count];
  226. }
  227. - (NSString *)description {
  228. NSString *result =
  229. [NSString stringWithFormat:@"%@<%p>{ port=%d localHostOnly=%@ status=%@ }", [self class],
  230. self, port_, (localhostOnly_ ? @"YES" : @"NO"),
  231. (listenHandle_ != nil ? @"Started" : @"Stopped")];
  232. return result;
  233. }
  234. @end
  235. @implementation GTMHTTPServer (PrivateMethods)
  236. - (void)acceptedConnectionNotification:(NSNotification *)notification {
  237. NSDictionary *userInfo = [notification userInfo];
  238. NSFileHandle *newConnection = [userInfo objectForKey:NSFileHandleNotificationFileHandleItem];
  239. NSAssert1(newConnection != nil, @"failed to get the connection in the notification: %@",
  240. notification);
  241. // make sure we accept more...
  242. [listenHandle_ acceptConnectionInBackgroundAndNotify];
  243. // TODO: could let the delegate look at the address, before we start working
  244. // on it.
  245. NSMutableDictionary *connDict = [self connectionWithFileHandle:newConnection];
  246. [connections_ addObject:connDict];
  247. }
  248. - (NSMutableDictionary *)connectionWithFileHandle:(NSFileHandle *)fileHandle {
  249. NSMutableDictionary *result = [NSMutableDictionary dictionary];
  250. [result setObject:fileHandle forKey:kFileHandle];
  251. GTMHTTPRequestMessage *request = [[[GTMHTTPRequestMessage alloc] init] autorelease];
  252. [result setObject:request forKey:kRequest];
  253. // setup for data notifications
  254. NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
  255. [center addObserver:self
  256. selector:@selector(dataAvailableNotification:)
  257. name:NSFileHandleReadCompletionNotification
  258. object:fileHandle];
  259. [fileHandle readInBackgroundAndNotify];
  260. return result;
  261. }
  262. - (void)dataAvailableNotification:(NSNotification *)notification {
  263. NSFileHandle *connectionHandle = GTM_STATIC_CAST(NSFileHandle, [notification object]);
  264. NSMutableDictionary *connDict = [self lookupConnection:connectionHandle];
  265. if (connDict == nil) return; // we are no longer tracking this one
  266. NSDictionary *userInfo = [notification userInfo];
  267. NSData *readData = [userInfo objectForKey:NSFileHandleNotificationDataItem];
  268. if ([readData length] == 0) {
  269. // remote side closed
  270. [self closeConnection:connDict];
  271. return;
  272. }
  273. // Use a local pool to keep memory down incase the runloop we're in doesn't
  274. // drain until it gets a UI event.
  275. NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
  276. @try {
  277. // Like Apple's sample, we just keep adding data until we get a full header
  278. // and any referenced body.
  279. GTMHTTPRequestMessage *request = [connDict objectForKey:kRequest];
  280. [request appendData:readData];
  281. // Is the header complete yet?
  282. if (![request isHeaderComplete]) {
  283. // more data...
  284. [connectionHandle readInBackgroundAndNotify];
  285. } else {
  286. // Do we have all the body?
  287. UInt32 contentLength = [request contentLength];
  288. NSData *body = [request body];
  289. NSUInteger bodyLength = [body length];
  290. if (contentLength > bodyLength) {
  291. // need more data...
  292. [connectionHandle readInBackgroundAndNotify];
  293. } else {
  294. if (contentLength < bodyLength) {
  295. // We got extra (probably someone trying to pipeline on us), trim
  296. // and let the extra data go...
  297. NSData *newBody = [NSData dataWithBytes:[body bytes] length:contentLength];
  298. [request setBody:newBody];
  299. _GTMDevLog(@"Got %lu extra bytes on http request, ignoring them",
  300. (unsigned long)(bodyLength - contentLength));
  301. }
  302. GTMHTTPResponseMessage *response = nil;
  303. @try {
  304. // Off to the delegate
  305. response = [delegate_ httpServer:self handleRequest:request];
  306. } @catch (NSException *e) {
  307. _GTMDevLog(@"Exception trying to handle http request: %@", e);
  308. } // COV_NF_LINE - radar 5851992 only reachable w/ an uncaught exception which isn't
  309. // testable
  310. if (response) {
  311. // We don't support connection reuse, so we add (force) the header to
  312. // close every connection.
  313. [response setValue:@"close" forHeaderField:@"Connection"];
  314. // spawn thread to send reply (since we do a blocking send)
  315. [connDict setObject:response forKey:kResponse];
  316. [NSThread detachNewThreadSelector:@selector(sendResponseOnNewThread:)
  317. toTarget:self
  318. withObject:connDict];
  319. } else {
  320. // No response, shut it down
  321. [self closeConnection:connDict];
  322. }
  323. }
  324. }
  325. } @catch (NSException *e) { // COV_NF_START
  326. _GTMDevLog(@"exception while read data: %@", e);
  327. // exception while dealing with the connection, close it
  328. } // COV_NF_END
  329. @finally {
  330. [pool drain];
  331. }
  332. }
  333. - (NSMutableDictionary *)lookupConnection:(NSFileHandle *)fileHandle {
  334. NSMutableDictionary *result = nil;
  335. for (NSMutableDictionary *connDict in connections_) {
  336. if (fileHandle == [connDict objectForKey:kFileHandle]) {
  337. result = connDict;
  338. break;
  339. }
  340. }
  341. return result;
  342. }
  343. - (void)closeConnection:(NSMutableDictionary *)connDict {
  344. // remove the notification
  345. NSFileHandle *connectionHandle = [connDict objectForKey:kFileHandle];
  346. NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
  347. [center removeObserver:self name:NSFileHandleReadCompletionNotification object:connectionHandle];
  348. // in a non GC world, we're fine just letting the connect get closed when
  349. // the object is release when it comes out of connections_, but in a GC world
  350. // it won't get cleaned up
  351. [connectionHandle closeFile];
  352. // remove it from the list
  353. [connections_ removeObject:connDict];
  354. }
  355. - (void)sendResponseOnNewThread:(NSMutableDictionary *)connDict {
  356. NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
  357. @try {
  358. GTMHTTPResponseMessage *response = [connDict objectForKey:kResponse];
  359. NSFileHandle *connectionHandle = [connDict objectForKey:kFileHandle];
  360. NSData *serialized = [response serializedData];
  361. [connectionHandle writeData:serialized];
  362. } @catch (NSException *e) { // COV_NF_START - causing an exception here is to hard in a test
  363. // TODO: let the delegate know about the exception (but do it on the main
  364. // thread)
  365. _GTMDevLog(@"exception while sending reply: %@", e);
  366. } // COV_NF_END
  367. // back to the main thread to close things down
  368. [self performSelectorOnMainThread:@selector(sentResponse:) withObject:connDict waitUntilDone:NO];
  369. [pool release];
  370. }
  371. - (void)sentResponse:(NSMutableDictionary *)connDict {
  372. // make sure we're still tracking this connection (in case server was stopped)
  373. NSFileHandle *connection = [connDict objectForKey:kFileHandle];
  374. NSMutableDictionary *connDict2 = [self lookupConnection:connection];
  375. if (connDict != connDict2) return;
  376. // TODO: message the delegate that it was sent
  377. // close it down
  378. [self closeConnection:connDict];
  379. }
  380. @end
  381. #pragma mark -
  382. @implementation GTMHTTPRequestMessage
  383. - (id)init {
  384. self = [super init];
  385. if (self) {
  386. message_ = CFHTTPMessageCreateEmpty(kCFAllocatorDefault, YES);
  387. }
  388. return self;
  389. }
  390. - (void)dealloc {
  391. if (message_) {
  392. CFRelease(message_);
  393. }
  394. [super dealloc];
  395. }
  396. - (NSString *)version {
  397. return GTMCFAutorelease(CFHTTPMessageCopyVersion(message_));
  398. }
  399. - (NSURL *)URL {
  400. return GTMCFAutorelease(CFHTTPMessageCopyRequestURL(message_));
  401. }
  402. - (NSString *)method {
  403. return GTMCFAutorelease(CFHTTPMessageCopyRequestMethod(message_));
  404. }
  405. - (NSData *)body {
  406. return GTMCFAutorelease(CFHTTPMessageCopyBody(message_));
  407. }
  408. - (NSDictionary *)allHeaderFieldValues {
  409. return GTMCFAutorelease(CFHTTPMessageCopyAllHeaderFields(message_));
  410. }
  411. - (NSString *)description {
  412. CFStringRef desc = CFCopyDescription(message_);
  413. NSString *result = [NSString stringWithFormat:@"%@<%p>{ message=%@ }", [self class], self, desc];
  414. CFRelease(desc);
  415. return result;
  416. }
  417. @end
  418. @implementation GTMHTTPRequestMessage (PrivateHelpers)
  419. - (BOOL)isHeaderComplete {
  420. return CFHTTPMessageIsHeaderComplete(message_) ? YES : NO;
  421. }
  422. - (BOOL)appendData:(NSData *)data {
  423. return CFHTTPMessageAppendBytes(message_, [data bytes], (CFIndex)[data length]) ? YES : NO;
  424. }
  425. - (NSString *)headerFieldValueForKey:(NSString *)key {
  426. CFStringRef value = NULL;
  427. if (key) {
  428. value = CFHTTPMessageCopyHeaderFieldValue(message_, (CFStringRef)key);
  429. }
  430. return GTMCFAutorelease(value);
  431. }
  432. - (UInt32)contentLength {
  433. return (UInt32)[[self headerFieldValueForKey:@"Content-Length"] intValue];
  434. }
  435. - (void)setBody:(NSData *)body {
  436. if (!body) {
  437. body = [NSData data]; // COV_NF_LINE - can only happen in we fail to make the new data object
  438. }
  439. CFHTTPMessageSetBody(message_, (CFDataRef)body);
  440. }
  441. @end
  442. #pragma mark -
  443. @implementation GTMHTTPResponseMessage
  444. - (id)init {
  445. return [self initWithBody:nil contentType:nil statusCode:0];
  446. }
  447. - (id)initWithBody:(NSData *)body contentType:(NSString *)contentType statusCode:(int)statusCode {
  448. self = [super init];
  449. if (self) {
  450. if ((statusCode < 100) || (statusCode > 599)) {
  451. [self release];
  452. return nil;
  453. }
  454. message_ =
  455. CFHTTPMessageCreateResponse(kCFAllocatorDefault, statusCode, NULL, kCFHTTPVersion1_0);
  456. if (!message_) {
  457. // COV_NF_START
  458. [self release];
  459. return nil;
  460. // COV_NF_END
  461. }
  462. NSUInteger bodyLength = 0;
  463. if (body) {
  464. bodyLength = [body length];
  465. CFHTTPMessageSetBody(message_, (CFDataRef)body);
  466. }
  467. if ([contentType length] == 0) {
  468. contentType = @"text/html";
  469. }
  470. NSString *bodyLenStr = [NSString stringWithFormat:@"%lu", (unsigned long)bodyLength];
  471. [self setValue:bodyLenStr forHeaderField:@"Content-Length"];
  472. [self setValue:contentType forHeaderField:@"Content-Type"];
  473. }
  474. return self;
  475. }
  476. - (void)dealloc {
  477. if (message_) {
  478. CFRelease(message_);
  479. }
  480. [super dealloc];
  481. }
  482. + (instancetype)responseWithString:(NSString *)plainText {
  483. NSData *body = [plainText dataUsingEncoding:NSUTF8StringEncoding];
  484. return [self responseWithBody:body contentType:@"text/plain; charset=UTF-8" statusCode:200];
  485. }
  486. + (instancetype)responseWithHTMLString:(NSString *)htmlString {
  487. return [self responseWithBody:[htmlString dataUsingEncoding:NSUTF8StringEncoding]
  488. contentType:@"text/html; charset=UTF-8"
  489. statusCode:200];
  490. }
  491. + (instancetype)responseWithBody:(NSData *)body
  492. contentType:(NSString *)contentType
  493. statusCode:(int)statusCode {
  494. return [[[[self class] alloc] initWithBody:body contentType:contentType statusCode:statusCode]
  495. autorelease];
  496. }
  497. + (instancetype)emptyResponseWithCode:(int)statusCode {
  498. return
  499. [[[[self class] alloc] initWithBody:nil contentType:nil statusCode:statusCode] autorelease];
  500. }
  501. - (void)setValue:(NSString *)value forHeaderField:(NSString *)headerField {
  502. if ([headerField length] == 0) return;
  503. if (value == nil) {
  504. value = @"";
  505. }
  506. CFHTTPMessageSetHeaderFieldValue(message_, (CFStringRef)headerField, (CFStringRef)value);
  507. }
  508. - (void)setHeaderValuesFromDictionary:(NSDictionary *)dict {
  509. for (id key in dict) {
  510. id value = [dict valueForKey:key];
  511. [self setValue:value forHeaderField:key];
  512. }
  513. }
  514. - (NSString *)description {
  515. CFStringRef desc = CFCopyDescription(message_);
  516. NSString *result = [NSString stringWithFormat:@"%@<%p>{ message=%@ }", [self class], self, desc];
  517. CFRelease(desc);
  518. return result;
  519. }
  520. - (NSData *)serializedData {
  521. return GTMCFAutorelease(CFHTTPMessageCopySerializedMessage(message_));
  522. }
  523. @end