Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(217)

Side by Side Diff: lib/src/sync_http.dart

Issue 2827083002: Created a new synchronous http client using RawSynchronousSockets. (Closed)
Patch Set: Created a new synchronous http client using RawSynchronousSockets. Created 3 years, 8 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(Empty)
1 // Copyright (c) 2017, the Dart project authors. Please see the AUTHORS file
2 // for details. All rights reserved. Use of this source code is governed by a
3 // BSD-style license that can be found in the LICENSE file.
4
5 part of sync.http;
6
7 /// A simple synchronous HTTP client.
8 ///
9 /// This is a two-step process. When a [SyncHttpClientRequest] is returned the
10 /// underlying network connection has been established, but no data has yet been
11 /// sent. The HTTP headers and body can be set on the request, and close is
12 /// called to send it to the server and get the [SyncHttpClientResponse].
13 abstract class SyncHttpClient {
14 /// Send a GET request to the provided URL.
15 static SyncHttpClientRequest getUrl(Uri uri) =>
16 new SyncHttpClientRequest._('GET', uri, false);
17
18 /// Send a POST request to the provided URL.
19 static SyncHttpClientRequest postUrl(uri) =>
20 new SyncHttpClientRequest._('POST', uri, true);
21
22 /// Send a DELETE request to the provided URL.
23 static SyncHttpClientRequest deleteUrl(uri) =>
24 new SyncHttpClientRequest._('DELETE', uri, false);
25
26 /// Send a PUT request to the provided URL.
27 static SyncHttpClientRequest putUrl(uri) =>
28 new SyncHttpClientRequest._('PUT', uri, true);
29 }
30
31 /// HTTP request for a synchronous client connection.
32 class SyncHttpClientRequest {
33 static const String _protocolVersion = '1.1';
34
35 int get contentLength => hasBody ? _body.length : null;
zra 2017/04/19 22:42:43 Please document all public fields and methods. Her
bkonyi 2017/04/20 14:40:48 Done (I think I got them all?).
36
37 HttpHeaders _headers;
38
39 /// The headers associated with the HTTP request.
40 HttpHeaders get headers {
41 if (_headers == null) {
42 _headers = new _SyncHttpClientRequestHeaders(this);
43 }
44 return _headers;
45 }
46
47 final String method;
48
49 final Uri uri;
50
51 final Encoding encoding = UTF8;
52
53 final BytesBuilder _body;
54
55 final RawSynchronousSocket _socket;
56
57 SyncHttpClientRequest._(this.method, Uri uri, bool body)
58 : this.uri = uri,
59 this._body = body ? new BytesBuilder() : null,
60 this._socket = RawSynchronousSocket.connectSync(uri.host, uri.port);
61
62 /// Write content into the body of the request.
63 void write(Object obj) {
64 if (hasBody) {
65 _body.add(encoding.encoder.convert(obj.toString()));
66 } else {
67 throw new StateError('write not allowed for method $method');
68 }
69 }
70
71 bool get hasBody => _body != null;
72
73 /// Send the HTTP request and get the response.
74 SyncHttpClientResponse close() {
75 StringBuffer buffer = new StringBuffer();
76 buffer.write('$method ${uri.path} HTTP/$_protocolVersion\r\n');
77 headers.forEach((name, values) {
78 values.forEach((value) {
79 buffer.write('$name: $value\r\n');
80 });
81 });
82 buffer.write('\r\n');
83 if (hasBody) {
84 buffer.write(new String.fromCharCodes(_body.takeBytes()));
85 }
86 _socket.writeFromSync(buffer.toString().codeUnits);
87 return new SyncHttpClientResponse(_socket);
88 }
89 }
90
91 class _SyncHttpClientRequestHeaders implements HttpHeaders {
92 Map<String, List> _headers = <String, List<String>>{};
93
94 final SyncHttpClientRequest _request;
95 ContentType contentType;
96
97 _SyncHttpClientRequestHeaders(this._request);
98
99 @override
100 List<String> operator [](String name) {
101 switch (name) {
102 case HttpHeaders.ACCEPT_CHARSET:
103 return ['utf-8'];
104 case HttpHeaders.ACCEPT_ENCODING:
105 return ['identity'];
106 case HttpHeaders.CONNECTION:
107 return ['close'];
108 case HttpHeaders.CONTENT_LENGTH:
109 if (!_request.hasBody) {
110 return null;
111 }
112 return [contentLength.toString()];
113 case HttpHeaders.CONTENT_TYPE:
114 if (contentType == null) {
115 return null;
116 }
117 return [contentType.toString()];
118 case HttpHeaders.HOST:
119 return ['$host:$port'];
120 default:
121 var values = _headers[name];
122 if (values == null || values.isEmpty) {
123 return null;
124 }
125 return values.map((e) => e.toString()).toList(growable: false);
126 }
127 }
128
129 /// Add [value] to the list of values associated with header [name].
130 @override
131 void add(String name, Object value) {
132 switch (name) {
133 case HttpHeaders.ACCEPT_CHARSET:
134 case HttpHeaders.ACCEPT_ENCODING:
135 case HttpHeaders.CONNECTION:
136 case HttpHeaders.CONTENT_LENGTH:
137 case HttpHeaders.DATE:
138 case HttpHeaders.EXPIRES:
139 case HttpHeaders.IF_MODIFIED_SINCE:
140 case HttpHeaders.HOST:
141 throw new UnsupportedError('Unsupported or immutable property: $name');
142 case HttpHeaders.CONTENT_TYPE:
143 contentType = value;
144 break;
145 default:
146 if (_headers[name] == null) {
147 _headers[name] = [];
148 }
149 _headers[name].add(value);
150 }
151 }
152
153 /// Remove [value] from the list associated with header [name].
154 @override
155 void remove(String name, Object value) {
156 switch (name) {
157 case HttpHeaders.ACCEPT_CHARSET:
158 case HttpHeaders.ACCEPT_ENCODING:
159 case HttpHeaders.CONNECTION:
160 case HttpHeaders.CONTENT_LENGTH:
161 case HttpHeaders.DATE:
162 case HttpHeaders.EXPIRES:
163 case HttpHeaders.IF_MODIFIED_SINCE:
164 case HttpHeaders.HOST:
165 throw new UnsupportedError('Unsupported or immutable property: $name');
166 case HttpHeaders.CONTENT_TYPE:
167 if (contentType == value) {
168 contentType = null;
169 }
170 break;
171 default:
172 if (_headers[name] != null) {
173 _headers[name].remove(value);
174 if (_headers[name].isEmpty) {
175 _headers.remove(name);
176 }
177 }
178 }
179 }
180
181 /// Remove all headers associated with key [name].
182 @override
183 void removeAll(String name) {
184 switch (name) {
185 case HttpHeaders.ACCEPT_CHARSET:
186 case HttpHeaders.ACCEPT_ENCODING:
187 case HttpHeaders.CONNECTION:
188 case HttpHeaders.CONTENT_LENGTH:
189 case HttpHeaders.DATE:
190 case HttpHeaders.EXPIRES:
191 case HttpHeaders.IF_MODIFIED_SINCE:
192 case HttpHeaders.HOST:
193 throw new UnsupportedError('Unsupported or immutable property: $name');
194 case HttpHeaders.CONTENT_TYPE:
195 contentType = null;
196 break;
197 default:
198 _headers.remove(name);
199 }
200 }
201
202 /// Replace values associated with key [name] with [value].
203 @override
204 void set(String name, Object value) {
205 removeAll(name);
206 add(name, value);
207 }
208
209 /// Returns the values associated with key [name], if it exists, otherwise
210 /// returns null.
211 @override
212 String value(String name) {
213 var val = this[name];
214 if (val == null || val.isEmpty) {
215 return null;
216 } else if (val.length == 1) {
217 return val[0];
218 } else {
219 throw new HttpException('header $name has more than one value');
220 }
221 }
222
223 /// Iterates over all header key-value pairs and applies [f].
224 @override
225 void forEach(void f(String name, List<String> values)) {
226 var forEachFunc = (String name) {
227 var values = this[name];
228 if (values != null && values.isNotEmpty) {
229 f(name, values);
230 }
231 };
232
233 [
234 HttpHeaders.ACCEPT_CHARSET,
235 HttpHeaders.ACCEPT_ENCODING,
236 HttpHeaders.CONNECTION,
237 HttpHeaders.CONTENT_LENGTH,
238 HttpHeaders.CONTENT_TYPE,
239 HttpHeaders.HOST
240 ].forEach(forEachFunc);
241 _headers.keys.forEach(forEachFunc);
242 }
243
244 @override
245 bool get chunkedTransferEncoding => null;
246
247 @override
248 void set chunkedTransferEncoding(bool _chunkedTransferEncoding) {
249 throw new UnsupportedError('chunked transfer is unsupported');
250 }
251
252 @override
253 int get contentLength => _request.contentLength;
254
255 @override
256 void set contentLength(int _contentLength) {
257 throw new UnsupportedError('content length is automatically set');
258 }
259
260 @override
261 void set date(DateTime _date) {
262 throw new UnsupportedError('date is unsupported');
263 }
264
265 @override
266 DateTime get date => null;
267
268 @override
269 void set expires(DateTime _expires) {
270 throw new UnsupportedError('expires is unsupported');
271 }
272
273 @override
274 DateTime get expires => null;
275
276 @override
277 void set host(String _host) {
278 throw new UnsupportedError('host is automatically set');
279 }
280
281 @override
282 String get host => _request.uri.host;
283
284 @override
285 DateTime get ifModifiedSince => null;
286
287 @override
288 void set ifModifiedSince(DateTime _ifModifiedSince) {
289 throw new UnsupportedError('if modified since is unsupported');
290 }
291
292 @override
293 void noFolding(String name) {
294 throw new UnsupportedError('no folding is unsupported');
295 }
296
297 @override
298 bool get persistentConnection => false;
299
300 @override
301 void set persistentConnection(bool _persistentConnection) {
302 throw new UnsupportedError('persistence connections are unsupported');
303 }
304
305 @override
306 void set port(int _port) {
307 throw new UnsupportedError('port is automatically set');
308 }
309
310 @override
311 int get port => _request.uri.port;
312
313 /// Clear all header key-value pairs.
314 @override
315 void clear() {
316 contentType = null;
317 _headers.clear();
318 }
319 }
320
321 /// HTTP response for a client connection.
322 class SyncHttpClientResponse {
323 int get contentLength => headers.contentLength;
324 final HttpHeaders headers;
325 final String reasonPhrase;
326 final int statusCode;
327 final String body;
328
329 factory SyncHttpClientResponse(RawSynchronousSocket socket) {
330 int statusCode;
331 String reasonPhrase;
332 StringBuffer body = new StringBuffer();
333 Map<String, List<String>> headers = {};
334
335 bool inHeader = false;
336 bool inBody = false;
337 int contentLength = 0;
338 int contentRead = 0;
339
340 void processLine(String line, int bytesRead, _LineDecoder decoder) {
341 if (inBody) {
342 body.write(line);
343 contentRead += bytesRead;
344 } else if (inHeader) {
345 if (line.trim().isEmpty) {
346 inBody = true;
347 if (contentLength > 0) {
348 decoder.expectedByteCount = contentLength;
349 }
350 return;
351 }
352 int separator = line.indexOf(':');
353 String name = line.substring(0, separator).toLowerCase().trim();
354 String value = line.substring(separator + 1).trim();
355 if (name == HttpHeaders.TRANSFER_ENCODING &&
356 value.toLowerCase() != 'identity') {
357 throw new UnsupportedError(
358 'only identity transfer encoding is accepted');
359 }
360 if (name == HttpHeaders.CONTENT_LENGTH) {
361 contentLength = int.parse(value);
362 }
363 if (!headers.containsKey(name)) {
364 headers[name] = [];
365 }
366 headers[name].add(value);
367 } else if (line.startsWith('HTTP/1.1') || line.startsWith('HTTP/1.0')) {
368 statusCode = int
369 .parse(line.substring('HTTP/1.x '.length, 'HTTP/1.x xxx'.length));
370 reasonPhrase = line.substring('HTTP/1.x xxx '.length);
371 inHeader = true;
372 } else {
373 throw new UnsupportedError('unsupported http response format');
374 }
375 }
376
377 var lineDecoder = new _LineDecoder.withCallback(processLine);
378
379 try {
380 while (!inHeader ||
381 !inBody ||
382 ((contentRead + lineDecoder.bufferedBytes) < contentLength)) {
383 var bytes = socket.readSync(1024);
384
385 if (bytes == null || bytes.length == 0) {
386 break;
387 }
388 lineDecoder.add(bytes);
389 }
390 } finally {
391 try {
392 lineDecoder.close();
393 } finally {
394 socket.closeSync();
395 }
396 }
397
398 return new SyncHttpClientResponse._(
399 reasonPhrase: reasonPhrase,
400 statusCode: statusCode,
401 body: body.toString(),
402 headers: headers);
403 }
404
405 SyncHttpClientResponse._(
406 {this.reasonPhrase, this.statusCode, this.body, headers})
407 : this.headers = new _SyncHttpClientResponseHeaders(headers);
408 }
409
410 class _SyncHttpClientResponseHeaders implements HttpHeaders {
411 final Map<String, List<String>> _headers;
412
413 _SyncHttpClientResponseHeaders(this._headers);
414
415 @override
416 List<String> operator [](String name) => _headers[name];
417
418 @override
419 void add(String name, Object value) {
420 throw new UnsupportedError('Response headers are immutable');
421 }
422
423 @override
424 bool get chunkedTransferEncoding => null;
425
426 @override
427 void set chunkedTransferEncoding(bool _chunkedTransferEncoding) {
428 throw new UnsupportedError('Response headers are immutable');
429 }
430
431 @override
432 int get contentLength {
433 String val = value(HttpHeaders.CONTENT_LENGTH);
434 if (val != null) {
435 return int.parse(val, onError: (_) => null);
436 }
437 return null;
438 }
439
440 @override
441 void set contentLength(int _contentLength) {
442 throw new UnsupportedError('Response headers are immutable');
443 }
444
445 @override
446 ContentType get contentType {
447 var val = value(HttpHeaders.CONTENT_TYPE);
448 if (val != null) {
449 return ContentType.parse(val);
450 }
451 return null;
452 }
453
454 @override
455 void set contentType(ContentType _contentType) {
456 throw new UnsupportedError('Response headers are immutable');
457 }
458
459 @override
460 void set date(DateTime _date) {
461 throw new UnsupportedError('Response headers are immutable');
462 }
463
464 @override
465 DateTime get date {
466 var val = value(HttpHeaders.DATE);
467 if (val != null) {
468 return DateTime.parse(val);
469 }
470 return null;
471 }
472
473 @override
474 void set expires(DateTime _expires) {
475 throw new UnsupportedError('Response headers are immutable');
476 }
477
478 @override
479 DateTime get expires {
480 var val = value(HttpHeaders.EXPIRES);
481 if (val != null) {
482 return DateTime.parse(val);
483 }
484 return null;
485 }
486
487 @override
488 void forEach(void f(String name, List<String> values)) => _headers.forEach(f);
489
490 @override
491 void set host(String _host) {
492 throw new UnsupportedError('Response headers are immutable');
493 }
494
495 @override
496 String get host {
497 var val = value(HttpHeaders.HOST);
498 if (val != null) {
499 return Uri.parse(val).host;
500 }
501 return null;
502 }
503
504 @override
505 DateTime get ifModifiedSince {
506 var val = value(HttpHeaders.IF_MODIFIED_SINCE);
507 if (val != null) {
508 return DateTime.parse(val);
509 }
510 return null;
511 }
512
513 @override
514 void set ifModifiedSince(DateTime _ifModifiedSince) {
515 throw new UnsupportedError('Response headers are immutable');
516 }
517
518 @override
519 void noFolding(String name) {
520 throw new UnsupportedError('Response headers are immutable');
521 }
522
523 @override
524 bool get persistentConnection => false;
525
526 @override
527 void set persistentConnection(bool _persistentConnection) {
528 throw new UnsupportedError('Response headers are immutable');
529 }
530
531 @override
532 void set port(int _port) {
533 throw new UnsupportedError('Response headers are immutable');
534 }
535
536 @override
537 int get port {
538 var val = value(HttpHeaders.HOST);
539 if (val != null) {
540 return Uri.parse(val).port;
541 }
542 return null;
543 }
544
545 @override
546 void remove(String name, Object value) {
547 throw new UnsupportedError('Response headers are immutable');
548 }
549
550 @override
551 void removeAll(String name) {
552 throw new UnsupportedError('Response headers are immutable');
553 }
554
555 @override
556 void set(String name, Object value) {
557 throw new UnsupportedError('Response headers are immutable');
558 }
559
560 @override
561 String value(String name) {
562 var val = this[name];
563 if (val == null || val.isEmpty) {
564 return null;
565 } else if (val.length == 1) {
566 return val[0];
567 } else {
568 throw new HttpException('header $name has more than one value');
569 }
570 }
571
572 @override
573 void clear() {
574 throw new UnsupportedError('Response headers are immutable');
575 }
576 }
OLDNEW
« no previous file with comments | « lib/src/line_decoder.dart ('k') | lib/sync_http.dart » ('j') | pubspec.yaml » ('J')

Powered by Google App Engine
This is Rietveld 408576698