ObjFW  Check-in [c35ba05bbc]

Overview
Comment:OFHTTPClient: Add support for passing a context
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | trunk
Files: files | file ages | folders
SHA3-256: c35ba05bbce42cc28e96988fe97dccb4d78da8b38e5bcecdc9530fe8e548b9d8
User & Date: js on 2017-09-25 00:02:44
Other Links: manifest | tags
Context
2017-09-25
21:31
TableGenerator: Use new async OFHTTPClient API check-in: adb82c292c user: js tags: trunk
00:02
OFHTTPClient: Add support for passing a context check-in: c35ba05bbc user: js tags: trunk
00:02
OFObject: Add -[performSelector*] with 4 objects check-in: f714793eb4 user: js tags: trunk
Changes

Modified src/OFHTTPClient.h from [e0785471fb] to [c9323a5119].

37
38
39
40
41
42
43


44
45
46
47

48
49
50
51
52
53
54
55


56
57
58
59

60
61
62
63
64
65
66
67
68
69
70
71
72


73
74
75
76

77
78
79
80
81
82
83
84
85


86
87
88
89
90

91
92
93
94
95
96
97
@protocol OFHTTPClientDelegate <OFObject>
/*!
 * @brief A callback which is called when an OFHTTPClient performed a request.
 *
 * @param client The OFHTTPClient which performed the request
 * @param request The request the OFHTTPClient performed
 * @param response The response to the request performed


 */
-      (void)client: (OFHTTPClient *)client
  didPerformRequest: (OFHTTPRequest *)request
	   response: (OFHTTPResponse *)response;


/*!
 * @brief A callback which is called when an OFHTTPClient encountered an
 *	  exception while performing a request.
 *
 * @param client The client which encountered an exception
 * @param exception The exception the client encountered
 * @param request The request during which the client encountered the exception


 */
-	   (void)client: (OFHTTPClient *)client
  didEncounterException: (id)exception
	     forRequest: (OFHTTPRequest *)request;


@optional
/*!
 * @brief A callback which is called when an OFHTTPClient creates a socket.
 *
 * This is useful if the connection is using HTTPS and the server requires a
 * client certificate. This callback can then be used to tell the TLS socket
 * about the certificate. Another use case is to tell the socket about a SOCKS5
 * proxy it should use for this connection.
 *
 * @param client The OFHTTPClient that created a socket
 * @param socket The socket created by the OFHTTPClient
 * @param request The request for which the socket was created


 */
-    (void)client: (OFHTTPClient *)client
  didCreateSocket: (OF_KINDOF(OFTCPSocket *))socket
       forRequest: (OFHTTPRequest *)request;


/*!
 * @brief A callback which is called when an OFHTTPClient received headers.
 *
 * @param client The OFHTTPClient which received the headers
 * @param headers The headers received
 * @param statusCode The status code received
 * @param request The request for which the headers and status code have been
 *		  received


 */
-      (void)client: (OFHTTPClient *)client
  didReceiveHeaders: (OFDictionary OF_GENERIC(OFString *, OFString *) *)headers
	 statusCode: (int)statusCode
	    request: (OFHTTPRequest *)request;


/*!
 * @brief A callback which is called when an OFHTTPClient wants to follow a
 *	  redirect.
 *
 * If you want to get the headers and data for each redirect, set the number of
 * redirects to 0 and perform a new OFHTTPClient for each redirect. However,







>
>



|
>








>
>



|
>













>
>



|
>









>
>




|
>







37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
@protocol OFHTTPClientDelegate <OFObject>
/*!
 * @brief A callback which is called when an OFHTTPClient performed a request.
 *
 * @param client The OFHTTPClient which performed the request
 * @param request The request the OFHTTPClient performed
 * @param response The response to the request performed
 * @param context The context object that was passed to
 *		  @ref asyncPerformRequest:context:
 */
-      (void)client: (OFHTTPClient *)client
  didPerformRequest: (OFHTTPRequest *)request
	   response: (OFHTTPResponse *)response
	    context: (nullable id)context;

/*!
 * @brief A callback which is called when an OFHTTPClient encountered an
 *	  exception while performing a request.
 *
 * @param client The client which encountered an exception
 * @param exception The exception the client encountered
 * @param request The request during which the client encountered the exception
 * @param context The context object that was passed to
 *		  @ref asyncPerformRequest:context:
 */
-	   (void)client: (OFHTTPClient *)client
  didEncounterException: (id)exception
	     forRequest: (OFHTTPRequest *)request
		context: (nullable id)context;

@optional
/*!
 * @brief A callback which is called when an OFHTTPClient creates a socket.
 *
 * This is useful if the connection is using HTTPS and the server requires a
 * client certificate. This callback can then be used to tell the TLS socket
 * about the certificate. Another use case is to tell the socket about a SOCKS5
 * proxy it should use for this connection.
 *
 * @param client The OFHTTPClient that created a socket
 * @param socket The socket created by the OFHTTPClient
 * @param request The request for which the socket was created
 * @param context The context object that was passed to
 *		  @ref asyncPerformRequest:context:
 */
-    (void)client: (OFHTTPClient *)client
  didCreateSocket: (OF_KINDOF(OFTCPSocket *))socket
       forRequest: (OFHTTPRequest *)request
	  context: (nullable id)context;

/*!
 * @brief A callback which is called when an OFHTTPClient received headers.
 *
 * @param client The OFHTTPClient which received the headers
 * @param headers The headers received
 * @param statusCode The status code received
 * @param request The request for which the headers and status code have been
 *		  received
 * @param context The context object that was passed to
 *		  @ref asyncPerformRequest:context:
 */
-      (void)client: (OFHTTPClient *)client
  didReceiveHeaders: (OFDictionary OF_GENERIC(OFString *, OFString *) *)headers
	 statusCode: (int)statusCode
	    request: (OFHTTPRequest *)request
	    context: (nullable id)context;

/*!
 * @brief A callback which is called when an OFHTTPClient wants to follow a
 *	  redirect.
 *
 * If you want to get the headers and data for each redirect, set the number of
 * redirects to 0 and perform a new OFHTTPClient for each redirect. However,
107
108
109
110
111
112
113


114
115
116
117
118
119
120

121
122
123
124
125
126
127
 * @param statusCode The status code for the redirection
 * @param request The request for which the OFHTTPClient wants to redirect.
 *		  You are allowed to change the request's headers from this
 *		  callback and they will be used when following the redirect
 *		  (e.g. to set the cookies for the new URL), however, keep in
 *		  mind that this will change the request you originally passed.
 * @param response The response indicating the redirect


 * @return A boolean whether the OFHTTPClient should follow the redirect
 */
-	  (bool)client: (OFHTTPClient *)client
  shouldFollowRedirect: (OFURL *)URL
	    statusCode: (int)statusCode
	       request: (OFHTTPRequest *)request
	      response: (OFHTTPResponse *)response;

@end

/*!
 * @class OFHTTPClient OFHTTPClient.h ObjFW/OFHTTPClient.h
 *
 * @brief A class for performing HTTP requests.
 */







>
>






|
>







119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
 * @param statusCode The status code for the redirection
 * @param request The request for which the OFHTTPClient wants to redirect.
 *		  You are allowed to change the request's headers from this
 *		  callback and they will be used when following the redirect
 *		  (e.g. to set the cookies for the new URL), however, keep in
 *		  mind that this will change the request you originally passed.
 * @param response The response indicating the redirect
 * @param context The context object that was passed to
 *		  @ref asyncPerformRequest:context:
 * @return A boolean whether the OFHTTPClient should follow the redirect
 */
-	  (bool)client: (OFHTTPClient *)client
  shouldFollowRedirect: (OFURL *)URL
	    statusCode: (int)statusCode
	       request: (OFHTTPRequest *)request
	      response: (OFHTTPResponse *)response
	       context: (nullable id)context;
@end

/*!
 * @class OFHTTPClient OFHTTPClient.h ObjFW/OFHTTPClient.h
 *
 * @brief A class for performing HTTP requests.
 */
154
155
156
157
158
159
160



161
162

163
164
165
166
167
168
169
170

171
172
173

174
175
176
177
178
179
180
181
 *
 * @return A new, autoreleased OFHTTPClient
 */
+ (instancetype)client;

/*!
 * @brief Asynchronously performs the specified HTTP request.



 */
- (void)asyncPerformRequest: (OFHTTPRequest *)request;


/*!
 * @brief Asynchronously performs the specified HTTP request.
 *
 * @param request The request to perform
 * @param redirects The maximum number of redirects after which no further
 *		    attempt is done to follow the redirect, but instead the
 *		    redirect is treated as an OFHTTPResponse

 */
- (void)asyncPerformRequest: (OFHTTPRequest *)request
		  redirects: (unsigned int)redirects;


/*!
 * @brief Closes connections that are still open due to keep-alive.
 */
- (void)close;
@end

OF_ASSUME_NONNULL_END







>
>
>

|
>








>


|
>








169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
 *
 * @return A new, autoreleased OFHTTPClient
 */
+ (instancetype)client;

/*!
 * @brief Asynchronously performs the specified HTTP request.
 *
 * @param request The request to perform
 * @param context A context object to be passed to the delegate
 */
- (void)asyncPerformRequest: (OFHTTPRequest *)request
		    context: (nullable id)context;

/*!
 * @brief Asynchronously performs the specified HTTP request.
 *
 * @param request The request to perform
 * @param redirects The maximum number of redirects after which no further
 *		    attempt is done to follow the redirect, but instead the
 *		    redirect is treated as an OFHTTPResponse
 * @param context A context object to be passed to the delegate
 */
- (void)asyncPerformRequest: (OFHTTPRequest *)request
		  redirects: (unsigned int)redirects
		    context: (nullable id)context;

/*!
 * @brief Closes connections that are still open due to keep-alive.
 */
- (void)close;
@end

OF_ASSUME_NONNULL_END

Modified src/OFHTTPClient.m from [3d75f15cfb] to [6841c42cc1].

45
46
47
48
49
50
51

52
53
54
55
56
57
58
59
60

61
62
63
64
65
66
67
#import "OFWriteFailedException.h"

@interface OFHTTPClientRequestHandler: OFObject
{
	OFHTTPClient *_client;
	OFHTTPRequest *_request;
	unsigned int _redirects;

	bool _firstLine;
	OFString *_version;
	int _status;
	OFMutableDictionary OF_GENERIC(OFString *, OFString *) *_serverHeaders;
}

- initWithClient: (OFHTTPClient *)client
	 request: (OFHTTPRequest *)request
       redirects: (unsigned int)redirects;

- (void)start;
@end

@interface OFHTTPClientResponse: OFHTTPResponse
{
	OFTCPSocket *_socket;
	bool _hasContentLength, _chunked, _keepAlive, _atEndOfStream;







>








|
>







45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
#import "OFWriteFailedException.h"

@interface OFHTTPClientRequestHandler: OFObject
{
	OFHTTPClient *_client;
	OFHTTPRequest *_request;
	unsigned int _redirects;
	id _context;
	bool _firstLine;
	OFString *_version;
	int _status;
	OFMutableDictionary OF_GENERIC(OFString *, OFString *) *_serverHeaders;
}

- initWithClient: (OFHTTPClient *)client
	 request: (OFHTTPRequest *)request
       redirects: (unsigned int)redirects
	 context: (id)context;
- (void)start;
@end

@interface OFHTTPClientResponse: OFHTTPResponse
{
	OFTCPSocket *_socket;
	bool _hasContentLength, _chunked, _keepAlive, _atEndOfStream;
201
202
203
204
205
206
207

208
209
210
211
212
213
214

215
216
217
218
219
220
221
222
223
224
225
226
227
228

229
230
231
232
233
234
235
	}
}

@implementation OFHTTPClientRequestHandler
- initWithClient: (OFHTTPClient *)client
	 request: (OFHTTPRequest *)request
       redirects: (unsigned int)redirects

{
	self = [super init];

	@try {
		_client = [client retain];
		_request = [request retain];
		_redirects = redirects;

		_firstLine = true;
		_serverHeaders = [[OFMutableDictionary alloc] init];
	} @catch (id e) {
		[self release];
		@throw e;
	}

	return self;
}

- (void)dealloc
{
	[_client release];
	[_request release];

	[_version release];
	[_serverHeaders release];

	[super dealloc];
}

- (void)closeAndReconnect







>







>














>







203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
	}
}

@implementation OFHTTPClientRequestHandler
- initWithClient: (OFHTTPClient *)client
	 request: (OFHTTPRequest *)request
       redirects: (unsigned int)redirects
	 context: (id)context
{
	self = [super init];

	@try {
		_client = [client retain];
		_request = [request retain];
		_redirects = redirects;
		_context = [context retain];
		_firstLine = true;
		_serverHeaders = [[OFMutableDictionary alloc] init];
	} @catch (id e) {
		[self release];
		@throw e;
	}

	return self;
}

- (void)dealloc
{
	[_client release];
	[_request release];
	[_context release];
	[_version release];
	[_serverHeaders release];

	[super dealloc];
}

- (void)closeAndReconnect
305
306
307
308
309
310
311
312
313
314
315
316
317
318

319
320
321
322
323
324
325
	    [location hasPrefix: @"https://"])) {
		OFURL *newURL;
		bool follow;

		newURL = [OFURL URLWithString: location
				relativeToURL: URL];

		if ([_client->_delegate respondsToSelector: @selector(
		    client:shouldFollowRedirect:statusCode:request:response:)])
			follow = [_client->_delegate client: _client
				       shouldFollowRedirect: newURL
						 statusCode: _status
						    request: _request
						   response: response];

		else {
			of_http_request_method_t method = [_request method];

			/*
			 * 301, 302 and 307 should only redirect with user
			 * confirmation if the request method is not GET or
			 * HEAD. Asking the delegate and getting true returned







|
|




|
>







310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
	    [location hasPrefix: @"https://"])) {
		OFURL *newURL;
		bool follow;

		newURL = [OFURL URLWithString: location
				relativeToURL: URL];

		if ([_client->_delegate respondsToSelector: @selector(client:
		    shouldFollowRedirect:statusCode:request:response:context:)])
			follow = [_client->_delegate client: _client
				       shouldFollowRedirect: newURL
						 statusCode: _status
						    request: _request
						   response: response
						    context: _context];
		else {
			of_http_request_method_t method = [_request method];

			/*
			 * 301, 302 and 307 should only redirect with user
			 * confirmation if the request method is not GET or
			 * HEAD. Asking the delegate and getting true returned
375
376
377
378
379
380
381
382

383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398

399
400
401
402
403
404
405

			[newRequest setURL: newURL];
			[newRequest setHeaders: newHeaders];

			_client->_inProgress = false;

			[_client asyncPerformRequest: newRequest
					   redirects: _redirects - 1];

			return;
		}
	}

	if (_status / 100 != 2)
		@throw [OFHTTPRequestFailedException
		    exceptionWithRequest: _request
				response: response];

	_client->_inProgress = false;

	[_client->_delegate performSelector: @selector(client:didPerformRequest:
						 response:)
				 withObject: _client
				 withObject: _request
				 withObject: response

				 afterDelay: 0];
}

- (bool)handleFirstLine: (OFString *)line
{
	/*
	 * It's possible that the write succeeds on a connection that is







|
>












|



>







381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413

			[newRequest setURL: newURL];
			[newRequest setHeaders: newHeaders];

			_client->_inProgress = false;

			[_client asyncPerformRequest: newRequest
					   redirects: _redirects - 1
					     context: _context];
			return;
		}
	}

	if (_status / 100 != 2)
		@throw [OFHTTPRequestFailedException
		    exceptionWithRequest: _request
				response: response];

	_client->_inProgress = false;

	[_client->_delegate performSelector: @selector(client:didPerformRequest:
						 response:context:)
				 withObject: _client
				 withObject: _request
				 withObject: response
				 withObject: _context
				 afterDelay: 0];
}

- (bool)handleFirstLine: (OFString *)line
{
	/*
	 * It's possible that the write succeeds on a connection that is
434
435
436
437
438
439
440
441
442
443
444
445
446

447
448
449
450
451
452
453

	if (line == nil)
		@throw [OFInvalidServerReplyException exception];

	if ([line length] == 0) {
		[_serverHeaders makeImmutable];

		if ([_client->_delegate respondsToSelector:
		    @selector(client:didReceiveHeaders:statusCode:request:)])
			[_client->_delegate client: _client
				 didReceiveHeaders: _serverHeaders
					statusCode: _status
					   request: _request];


		[self performSelector: @selector(createResponseWithSocket:)
			   withObject: socket
			   afterDelay: 0];

		return false;
	}







|
|



|
>







442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462

	if (line == nil)
		@throw [OFInvalidServerReplyException exception];

	if ([line length] == 0) {
		[_serverHeaders makeImmutable];

		if ([_client->_delegate respondsToSelector: @selector(client:
		    didReceiveHeaders:statusCode:request:context:)])
			[_client->_delegate client: _client
				 didReceiveHeaders: _serverHeaders
					statusCode: _status
					   request: _request
					   context: _context];

		[self performSelector: @selector(createResponseWithSocket:)
			   withObject: socket
			   afterDelay: 0];

		return false;
	}
497
498
499
500
501
502
503
504

505
506
507
508
509
510
511
512
513
514
515
516
517
518

519
520
521
522
523
524
525
526
527
528
529
530
531
532

533
534
535
536
537
538
539
	if (exception != nil) {
		if ([exception isKindOfClass:
		    [OFInvalidEncodingException class]])
			exception = [OFInvalidServerReplyException exception];

		[_client->_delegate client: _client
		     didEncounterException: exception
				forRequest: _request];

		return false;
	}

	@try {
		if (_firstLine) {
			_firstLine = false;
			return [self handleFirstLine: line];
		} else
			return [self handleServerHeader: line
						 socket: socket];
	} @catch (id e) {
		[_client->_delegate client: _client
		     didEncounterException: e
				forRequest: _request];

		return false;
	}
}

- (size_t)socket: (OFTCPSocket *)socket
    didWriteBody: (const void **)body
	  length: (size_t)length
	 context: (id)context
       exception: (id)exception
{
	if (exception != nil) {
		[_client->_delegate client: _client
		     didEncounterException: exception
				forRequest: _request];

		return 0;
	}

	[socket asyncReadLineWithTarget: self
			       selector: @selector(socket:didReadLine:context:
					      exception:)
				context: nil];







|
>













|
>













|
>







506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
	if (exception != nil) {
		if ([exception isKindOfClass:
		    [OFInvalidEncodingException class]])
			exception = [OFInvalidServerReplyException exception];

		[_client->_delegate client: _client
		     didEncounterException: exception
				forRequest: _request
				   context: _context];
		return false;
	}

	@try {
		if (_firstLine) {
			_firstLine = false;
			return [self handleFirstLine: line];
		} else
			return [self handleServerHeader: line
						 socket: socket];
	} @catch (id e) {
		[_client->_delegate client: _client
		     didEncounterException: e
				forRequest: _request
				   context: _context];
		return false;
	}
}

- (size_t)socket: (OFTCPSocket *)socket
    didWriteBody: (const void **)body
	  length: (size_t)length
	 context: (id)context
       exception: (id)exception
{
	if (exception != nil) {
		[_client->_delegate client: _client
		     didEncounterException: exception
				forRequest: _request
				   context: _context];
		return 0;
	}

	[socket asyncReadLineWithTarget: self
			       selector: @selector(socket:didReadLine:context:
					      exception:)
				context: nil];
555
556
557
558
559
560
561
562

563
564
565
566
567
568
569
			/* In case a keep-alive connection timed out */
			[self closeAndReconnect];
			return false;
		}

		[_client->_delegate client: _client
		     didEncounterException: exception
				forRequest: _request];

		return 0;
	}

	if ((body = [_request body]) != nil) {
		[socket asyncWriteBuffer: [body items]
				  length: [body count] * [body itemSize]
				  target: self







|
>







567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
			/* In case a keep-alive connection timed out */
			[self closeAndReconnect];
			return false;
		}

		[_client->_delegate client: _client
		     didEncounterException: exception
				forRequest: _request
				   context: _context];
		return 0;
	}

	if ((body = [_request body]) != nil) {
		[socket asyncWriteBuffer: [body items]
				  length: [body count] * [body itemSize]
				  target: self
604
605
606
607
608
609
610
611

612
613
614
615
616
617
618
619
620
621
622
623
624

625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640

641
642
643
644
645
646
647
				  target: self
				selector: @selector(socket:didWriteRequest:
					      length:context:exception:)
				 context: requestString];
	} @catch (id e) {
		[_client->_delegate client: _client
		     didEncounterException: e
				forRequest: _request];

		return;
	}
}

- (void)socketDidConnect: (OFTCPSocket *)socket
		 context: (id)context
	       exception: (id)exception
{
	if ([_client->_delegate respondsToSelector:
	    @selector(client:didCreateSocket:forRequest:)])
		[_client->_delegate client: _client
			   didCreateSocket: socket
				forRequest: _request];


	[self performSelector: @selector(handleSocket:)
		   withObject: socket
		   afterDelay: 0];
}

- (bool)throwAwayContent: (OFHTTPClientResponse *)response
		  buffer: (char *)buffer
		  length: (size_t)length
		 context: (OFTCPSocket *)socket
	       exception: (id)exception
{
	if (exception != nil) {
		[_client->_delegate client: _client
		     didEncounterException: exception
				forRequest: _request];

		return false;
	}

	if ([response isAtEndOfStream]) {
		[self freeMemory: buffer];

		[_client->_lastResponse release];







|
>









|


|
>















|
>







617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
				  target: self
				selector: @selector(socket:didWriteRequest:
					      length:context:exception:)
				 context: requestString];
	} @catch (id e) {
		[_client->_delegate client: _client
		     didEncounterException: e
				forRequest: _request
				   context: _context];
		return;
	}
}

- (void)socketDidConnect: (OFTCPSocket *)socket
		 context: (id)context
	       exception: (id)exception
{
	if ([_client->_delegate respondsToSelector:
	    @selector(client:didCreateSocket:forRequest:context:)])
		[_client->_delegate client: _client
			   didCreateSocket: socket
				forRequest: _request
				   context: _context];

	[self performSelector: @selector(handleSocket:)
		   withObject: socket
		   afterDelay: 0];
}

- (bool)throwAwayContent: (OFHTTPClientResponse *)response
		  buffer: (char *)buffer
		  length: (size_t)length
		 context: (OFTCPSocket *)socket
	       exception: (id)exception
{
	if (exception != nil) {
		[_client->_delegate client: _client
		     didEncounterException: exception
				forRequest: _request
				   context: _context];
		return false;
	}

	if ([response isAtEndOfStream]) {
		[self freeMemory: buffer];

		[_client->_lastResponse release];
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
			[_client->_lastResponse
			    asyncReadIntoBuffer: buffer
					 length: 512
					 target: self
				       selector: @selector(throwAwayContent:
						     buffer:length:context:
						     exception:)
				   context: socket];
		} else {
			[_client->_lastResponse release];
			_client->_lastResponse = nil;

			[self performSelector: @selector(handleSocket:)
				   withObject: socket
				   afterDelay: 0];







|







700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
			[_client->_lastResponse
			    asyncReadIntoBuffer: buffer
					 length: 512
					 target: self
				       selector: @selector(throwAwayContent:
						     buffer:length:context:
						     exception:)
					context: socket];
		} else {
			[_client->_lastResponse release];
			_client->_lastResponse = nil;

			[self performSelector: @selector(handleSocket:)
				   withObject: socket
				   afterDelay: 0];
912
913
914
915
916
917
918

919
920
921

922
923
924
925

926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943

944
945
946
947
948
949
950
{
	[self close];

	[super dealloc];
}

- (void)asyncPerformRequest: (OFHTTPRequest *)request

{
	[self asyncPerformRequest: request
			redirects: 10];

}

- (void)asyncPerformRequest: (OFHTTPRequest *)request
		  redirects: (unsigned int)redirects

{
	void *pool = objc_autoreleasePoolPush();
	OFURL *URL = [request URL];
	OFString *scheme = [URL scheme];

	if (![scheme isEqual: @"http"] && ![scheme isEqual: @"https"])
		@throw [OFUnsupportedProtocolException exceptionWithURL: URL];

	if (_inProgress)
		/* TODO: Find a better exception */
		@throw [OFAlreadyConnectedException exception];

	_inProgress = true;

	[[[[OFHTTPClientRequestHandler alloc]
	    initWithClient: self
		   request: request
		 redirects: redirects] autorelease] start];


	objc_autoreleasePoolPop(pool);
}

- (void)close
{
	[_socket release];







>


|
>




>

















|
>







928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
{
	[self close];

	[super dealloc];
}

- (void)asyncPerformRequest: (OFHTTPRequest *)request
		    context: (id)context
{
	[self asyncPerformRequest: request
			redirects: 10
			  context: context];
}

- (void)asyncPerformRequest: (OFHTTPRequest *)request
		  redirects: (unsigned int)redirects
		    context: (id)context
{
	void *pool = objc_autoreleasePoolPush();
	OFURL *URL = [request URL];
	OFString *scheme = [URL scheme];

	if (![scheme isEqual: @"http"] && ![scheme isEqual: @"https"])
		@throw [OFUnsupportedProtocolException exceptionWithURL: URL];

	if (_inProgress)
		/* TODO: Find a better exception */
		@throw [OFAlreadyConnectedException exception];

	_inProgress = true;

	[[[[OFHTTPClientRequestHandler alloc]
	    initWithClient: self
		   request: request
		 redirects: redirects
		   context: context] autorelease] start];

	objc_autoreleasePoolPop(pool);
}

- (void)close
{
	[_socket release];

Modified tests/OFHTTPClientTests.m from [1220c3f5ba] to [76843d6944].

90
91
92
93
94
95
96

97
98
99
100
101
102
103
}
@end

@implementation TestsAppDelegate (OFHTTPClientTests)
-      (void)client: (OFHTTPClient *)client
  didPerformRequest: (OFHTTPRequest *)request
	   response: (OFHTTPResponse *)response_

{
	response = [response_ retain];

	[[OFRunLoop mainRunLoop] stop];
}

- (void)HTTPClientTests







>







90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
}
@end

@implementation TestsAppDelegate (OFHTTPClientTests)
-      (void)client: (OFHTTPClient *)client
  didPerformRequest: (OFHTTPRequest *)request
	   response: (OFHTTPResponse *)response_
	    context: (id)context
{
	response = [response_ retain];

	[[OFRunLoop mainRunLoop] stop];
}

- (void)HTTPClientTests
121
122
123
124
125
126
127
128

129
130
131
132
133
134
135
	URL = [OFURL URLWithString:
	    [OFString stringWithFormat: @"http://127.0.0.1:%" @PRIu16 "/foo",
					server->_port]];

	TEST(@"-[asyncPerformRequest:]",
	    (client = [OFHTTPClient client]) && R([client setDelegate: self]) &&
	    R(request = [OFHTTPRequest requestWithURL: URL]) &&
	    R([client asyncPerformRequest: request]))


	[[OFRunLoop mainRunLoop] runUntilDate:
	    [OFDate dateWithTimeIntervalSinceNow: 2]];
	[response autorelease];

	TEST(@"Asynchronous handling of requests", response != nil)








|
>







122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
	URL = [OFURL URLWithString:
	    [OFString stringWithFormat: @"http://127.0.0.1:%" @PRIu16 "/foo",
					server->_port]];

	TEST(@"-[asyncPerformRequest:]",
	    (client = [OFHTTPClient client]) && R([client setDelegate: self]) &&
	    R(request = [OFHTTPRequest requestWithURL: URL]) &&
	    R([client asyncPerformRequest: request
				  context: nil]))

	[[OFRunLoop mainRunLoop] runUntilDate:
	    [OFDate dateWithTimeIntervalSinceNow: 2]];
	[response autorelease];

	TEST(@"Asynchronous handling of requests", response != nil)

Modified utils/ofhttp/OFHTTP.m from [7ea7ca97c5] to [e7a88133ce].

496
497
498
499
500
501
502

503
504
505
506
507
508
509
510
511
512
513

514
515
516
517
518
519
520
	[self performSelector: @selector(downloadNextURL)
		   afterDelay: 0];
}

-    (void)client: (OFHTTPClient *)client
  didCreateSocket: (OF_KINDOF(OFTCPSocket *))socket
	  request: (OFHTTPRequest *)request

{
	if (_insecure && [socket respondsToSelector:
	    @selector(setCertificateVerificationEnabled:)])
		[socket setCertificateVerificationEnabled: false];
}

-	  (bool)client: (OFHTTPClient *)client
  shouldFollowRedirect: (OFURL *)URL
	    statusCode: (int)statusCode
	       request: (OFHTTPRequest *)request
	      response: (OFHTTPResponse *)response

{
	if (!_quiet)
		[of_stdout writeFormat: @" ➜ %d\n", statusCode];

	if (_verbose) {
		void *pool = objc_autoreleasePoolPush();
		OFDictionary OF_GENERIC(OFString *, OFString *) *headers =







>











>







496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
	[self performSelector: @selector(downloadNextURL)
		   afterDelay: 0];
}

-    (void)client: (OFHTTPClient *)client
  didCreateSocket: (OF_KINDOF(OFTCPSocket *))socket
	  request: (OFHTTPRequest *)request
	  context: (id)context
{
	if (_insecure && [socket respondsToSelector:
	    @selector(setCertificateVerificationEnabled:)])
		[socket setCertificateVerificationEnabled: false];
}

-	  (bool)client: (OFHTTPClient *)client
  shouldFollowRedirect: (OFURL *)URL
	    statusCode: (int)statusCode
	       request: (OFHTTPRequest *)request
	      response: (OFHTTPResponse *)response
	       context: (id)context
{
	if (!_quiet)
		[of_stdout writeFormat: @" ➜ %d\n", statusCode];

	if (_verbose) {
		void *pool = objc_autoreleasePoolPush();
		OFDictionary OF_GENERIC(OFString *, OFString *) *headers =
537
538
539
540
541
542
543

544
545
546
547
548
549
550

	return true;
}

-	   (void)client: (OFHTTPClient *)client
  didEncounterException: (id)e
	     forRequest: (OFHTTPRequest *)request

{
	if ([e isKindOfClass: [OFAddressTranslationFailedException class]]) {
		if (!_quiet)
			[of_stdout writeString: @"\n"];

		[of_stderr writeLine:
		    OF_LOCALIZED(@"download_failed_address_translation",







>







539
540
541
542
543
544
545
546
547
548
549
550
551
552
553

	return true;
}

-	   (void)client: (OFHTTPClient *)client
  didEncounterException: (id)e
	     forRequest: (OFHTTPRequest *)request
		context: (id)context
{
	if ([e isKindOfClass: [OFAddressTranslationFailedException class]]) {
		if (!_quiet)
			[of_stdout writeString: @"\n"];

		[of_stderr writeLine:
		    OF_LOCALIZED(@"download_failed_address_translation",
624
625
626
627
628
629
630

631
632
633
634
635
636
637
638
639
640
641
642
643
	[self performSelector: @selector(downloadNextURL)
		   afterDelay: 0];
}

-      (void)client: (OFHTTPClient *)client
  didPerformRequest: (OFHTTPRequest *)request
	   response: (OFHTTPResponse *)response

{
	OFDictionary OF_GENERIC(OFString *, OFString *) *headers;
	OFString *lengthString, *type;

	/* Was a request to retrieve the file name */
	if (_detectFileName && _currentFileName == nil) {
		_currentFileName = [fileNameFromContentDisposition(
		    [[response headers] objectForKey: @"Content-Disposition"])
		    copy];
		_detectedFileName = true;

		if (!_quiet)
			[of_stdout writeFormat: @" ➜ %d\n",







>




<
|







627
628
629
630
631
632
633
634
635
636
637
638

639
640
641
642
643
644
645
646
	[self performSelector: @selector(downloadNextURL)
		   afterDelay: 0];
}

-      (void)client: (OFHTTPClient *)client
  didPerformRequest: (OFHTTPRequest *)request
	   response: (OFHTTPResponse *)response
	    context: (id)context
{
	OFDictionary OF_GENERIC(OFString *, OFString *) *headers;
	OFString *lengthString, *type;


	if ([context isEqual: @"detectFileName"]) {
		_currentFileName = [fileNameFromContentDisposition(
		    [[response headers] objectForKey: @"Content-Disposition"])
		    copy];
		_detectedFileName = true;

		if (!_quiet)
			[of_stdout writeFormat: @" ➜ %d\n",
904
905
906
907
908
909
910
911

912
913
914
915
916
917
918
		if (!_quiet)
			[of_stdout writeFormat: @"⠒ %@", [URL string]];

		request = [OFHTTPRequest requestWithURL: URL];
		[request setHeaders: clientHeaders];
		[request setMethod: OF_HTTP_REQUEST_METHOD_HEAD];

		[_HTTPClient asyncPerformRequest: request];

		return;
	}

	_detectedFileName = false;

	if (!_quiet)
		[of_stdout writeFormat: @"⇣ %@", [URL string]];







|
>







907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
		if (!_quiet)
			[of_stdout writeFormat: @"⠒ %@", [URL string]];

		request = [OFHTTPRequest requestWithURL: URL];
		[request setHeaders: clientHeaders];
		[request setMethod: OF_HTTP_REQUEST_METHOD_HEAD];

		[_HTTPClient asyncPerformRequest: request
					 context: @"detectFileName"];
		return;
	}

	_detectedFileName = false;

	if (!_quiet)
		[of_stdout writeFormat: @"⇣ %@", [URL string]];
945
946
947
948
949
950
951
952

953
954
955
956
957
958
959
	}

	request = [OFHTTPRequest requestWithURL: URL];
	[request setHeaders: clientHeaders];
	[request setMethod: _method];
	[request setBody: _body];

	[_HTTPClient asyncPerformRequest: request];

	return;

next:
	[self performSelector: @selector(downloadNextURL)
		   afterDelay: 0];
}
@end







|
>







949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
	}

	request = [OFHTTPRequest requestWithURL: URL];
	[request setHeaders: clientHeaders];
	[request setMethod: _method];
	[request setBody: _body];

	[_HTTPClient asyncPerformRequest: request
				 context: nil];
	return;

next:
	[self performSelector: @selector(downloadNextURL)
		   afterDelay: 0];
}
@end