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

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

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

Powered by Google App Engine
This is Rietveld 408576698