Chromium Code Reviews| OLD | NEW |
|---|---|
| (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 } | |
| OLD | NEW |