OLD | NEW |
(Empty) | |
| 1 // Copyright (c) 2014, 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 library http_wrapper; |
| 6 |
| 7 import 'dart:async'; |
| 8 import 'dart:io'; |
| 9 import 'dart:convert'; |
| 10 |
| 11 class AppengineHttpRequest implements HttpRequest { |
| 12 final HttpRequest _realRequest; |
| 13 AppengineHttpResponse _response; |
| 14 bool _hasSubscriber = false; |
| 15 |
| 16 AppengineHttpRequest(this._realRequest) { |
| 17 _response = new AppengineHttpResponse(this, _realRequest.response); |
| 18 } |
| 19 |
| 20 AppengineHttpResponse get response => _response; |
| 21 |
| 22 Future<bool> any(bool test(List<int> element)) { |
| 23 _hasSubscriber = true; |
| 24 return _realRequest.any(test); |
| 25 } |
| 26 |
| 27 Stream<List<int>> asBroadcastStream( |
| 28 {void onListen(StreamSubscription<List<int>> subscription), |
| 29 void onCancel(StreamSubscription<List<int>> subscription)}) { |
| 30 _hasSubscriber = true; |
| 31 return _realRequest.asBroadcastStream(onListen: onListen, |
| 32 onCancel: onCancel); |
| 33 } |
| 34 |
| 35 Stream asyncExpand(Stream convert(List<int> event)) { |
| 36 _hasSubscriber = true; |
| 37 return _realRequest.asyncExpand(convert); |
| 38 } |
| 39 |
| 40 Stream asyncMap(convert(List<int> event)) { |
| 41 _hasSubscriber = true; |
| 42 return _realRequest.asyncMap(convert); |
| 43 } |
| 44 |
| 45 Future<bool> contains(Object needle) { |
| 46 _hasSubscriber = true; |
| 47 return _realRequest.contains(needle); |
| 48 } |
| 49 |
| 50 Stream<List<int>> distinct( |
| 51 [bool equals(List<int> previous, List<int> next)]) { |
| 52 _hasSubscriber = true; |
| 53 return _realRequest.distinct(equals); |
| 54 } |
| 55 |
| 56 Future drain([futureValue]) { |
| 57 _hasSubscriber = true; |
| 58 return _realRequest.drain(futureValue); |
| 59 } |
| 60 |
| 61 Future<List<int>> elementAt(int index) { |
| 62 _hasSubscriber = true; |
| 63 return _realRequest.elementAt(index); |
| 64 } |
| 65 |
| 66 Future<bool> every(bool test(List<int> element)) { |
| 67 _hasSubscriber = true; |
| 68 return _realRequest.every(test); |
| 69 } |
| 70 |
| 71 Stream expand(Iterable convert(List<int> value)) { |
| 72 _hasSubscriber = true; |
| 73 return _realRequest.expand(convert); |
| 74 } |
| 75 |
| 76 Future<List<int>> get first { |
| 77 _hasSubscriber = true; |
| 78 return _realRequest.first; |
| 79 } |
| 80 |
| 81 Future firstWhere(bool test(List<int> element), {Object defaultValue()}) { |
| 82 _hasSubscriber = true; |
| 83 return _realRequest.firstWhere(test, defaultValue: defaultValue); |
| 84 } |
| 85 |
| 86 Future fold(initialValue, combine(previous, List<int> element)) { |
| 87 _hasSubscriber = true; |
| 88 return _realRequest.fold(initialValue, combine); |
| 89 } |
| 90 |
| 91 Future forEach(void action(List<int> element)) { |
| 92 _hasSubscriber = true; |
| 93 return _realRequest.forEach(action); |
| 94 } |
| 95 |
| 96 Stream<List<int>> handleError(Function onError, {bool test(error)}) { |
| 97 _hasSubscriber = true; |
| 98 return _realRequest.handleError(onError, test: test); |
| 99 } |
| 100 |
| 101 Future<bool> get isEmpty { |
| 102 _hasSubscriber = true; |
| 103 return _realRequest.isEmpty; |
| 104 } |
| 105 |
| 106 Future<String> join([String separator = ""]) { |
| 107 _hasSubscriber = true; |
| 108 return _realRequest.join(separator); |
| 109 } |
| 110 |
| 111 Future<List<int>> get last { |
| 112 _hasSubscriber = true; |
| 113 return _realRequest.last; |
| 114 } |
| 115 |
| 116 Future lastWhere(bool test(List<int> element), {Object defaultValue()}) { |
| 117 _hasSubscriber = true; |
| 118 return _realRequest.lastWhere(test, defaultValue: defaultValue); |
| 119 } |
| 120 |
| 121 Future<int> get length { |
| 122 _hasSubscriber = true; |
| 123 return _realRequest.length; |
| 124 } |
| 125 |
| 126 StreamSubscription<List<int>> listen(void onData(List<int> event), |
| 127 {Function onError, void onDone(), bool cancelOnError}) { |
| 128 _hasSubscriber = true; |
| 129 return _realRequest.listen(onData, |
| 130 onError: onError, |
| 131 onDone: onDone, |
| 132 cancelOnError: cancelOnError); |
| 133 } |
| 134 |
| 135 Stream map(convert(List<int> event)) { |
| 136 _hasSubscriber = true; |
| 137 return _realRequest.map(convert); |
| 138 } |
| 139 |
| 140 Future<List<int>> get single { |
| 141 _hasSubscriber = true; |
| 142 return _realRequest.single; |
| 143 } |
| 144 |
| 145 Future<List<int>> singleWhere(bool test(List<int> element)) { |
| 146 _hasSubscriber = true; |
| 147 return _realRequest.singleWhere(test); |
| 148 } |
| 149 |
| 150 Stream<List<int>> skip(int count) { |
| 151 _hasSubscriber = true; |
| 152 return _realRequest.skip(count); |
| 153 } |
| 154 |
| 155 Stream<List<int>> skipWhile(bool test(List<int> element)) { |
| 156 _hasSubscriber = true; |
| 157 return _realRequest.skipWhile(test); |
| 158 } |
| 159 |
| 160 Stream<List<int>> take(int count) { |
| 161 _hasSubscriber = true; |
| 162 return _realRequest.take(count); |
| 163 } |
| 164 |
| 165 Stream<List<int>> takeWhile(bool test(List<int> element)) { |
| 166 _hasSubscriber = true; |
| 167 return _realRequest.takeWhile(test); |
| 168 } |
| 169 |
| 170 Stream timeout(Duration timeLimit, {void onTimeout(EventSink sink)}) { |
| 171 _hasSubscriber = true; |
| 172 return _realRequest.timeout(timeLimit, onTimeout: onTimeout); |
| 173 } |
| 174 |
| 175 Future<List<List<int>>> toList() { |
| 176 _hasSubscriber = true; |
| 177 return _realRequest.toList(); |
| 178 } |
| 179 |
| 180 Future<Set<List<int>>> toSet() { |
| 181 _hasSubscriber = true; |
| 182 return _realRequest.toSet(); |
| 183 } |
| 184 |
| 185 Stream transform(StreamTransformer<List<int>, dynamic> streamTransformer) { |
| 186 _hasSubscriber = true; |
| 187 return _realRequest.transform(streamTransformer); |
| 188 } |
| 189 |
| 190 Stream<List<int>> where(bool test(List<int> event)) { |
| 191 _hasSubscriber = true; |
| 192 return _realRequest.where(test); |
| 193 } |
| 194 |
| 195 Future<List<int>> reduce( |
| 196 List<int> combine(List<int> previous, List<int> element)) { |
| 197 _hasSubscriber = true; |
| 198 return _realRequest.reduce(combine); |
| 199 } |
| 200 |
| 201 Future pipe(StreamConsumer<List<int>> streamConsumer) { |
| 202 _hasSubscriber = true; |
| 203 return _realRequest.pipe(streamConsumer); |
| 204 } |
| 205 |
| 206 Uri get uri => _realRequest.uri; |
| 207 |
| 208 X509Certificate get certificate => _realRequest.certificate; |
| 209 |
| 210 HttpConnectionInfo get connectionInfo => _realRequest.connectionInfo; |
| 211 |
| 212 int get contentLength => _realRequest.contentLength; |
| 213 |
| 214 List<Cookie> get cookies => _realRequest.cookies; |
| 215 |
| 216 HttpHeaders get headers => _realRequest.headers; |
| 217 |
| 218 bool get isBroadcast => _realRequest.isBroadcast; |
| 219 |
| 220 String get method => _realRequest.method; |
| 221 |
| 222 bool get persistentConnection => _realRequest.persistentConnection; |
| 223 |
| 224 String get protocolVersion => _realRequest.protocolVersion; |
| 225 |
| 226 Uri get requestedUri => _realRequest.requestedUri; |
| 227 |
| 228 HttpSession get session => _realRequest.session; |
| 229 } |
| 230 |
| 231 abstract class AppengineIOSinkMixin { |
| 232 void writeAll(Iterable objects, [String separator = ""]) { |
| 233 Iterator iterator = objects.iterator; |
| 234 if (!iterator.moveNext()) return; |
| 235 if (separator.isEmpty) { |
| 236 do { |
| 237 write(iterator.current); |
| 238 } while (iterator.moveNext()); |
| 239 } else { |
| 240 write(iterator.current); |
| 241 while (iterator.moveNext()) { |
| 242 write(separator); |
| 243 write(iterator.current); |
| 244 } |
| 245 } |
| 246 } |
| 247 |
| 248 void writeln([Object obj = ""]) { |
| 249 write(obj); |
| 250 write('\n'); |
| 251 } |
| 252 |
| 253 void writeCharCode(int charCode) { |
| 254 write(new String.fromCharCode(charCode)); |
| 255 } |
| 256 |
| 257 void write(Object obj) { |
| 258 add(encoding.encode('$obj')); |
| 259 } |
| 260 |
| 261 void add(List<int> data); |
| 262 |
| 263 Encoding get encoding; |
| 264 } |
| 265 |
| 266 class AppengineHttpResponse extends Object |
| 267 with AppengineIOSinkMixin |
| 268 implements HttpResponse { |
| 269 final AppengineHttpRequest _request; |
| 270 final HttpResponse _realResponse; |
| 271 AppengineHttpHeaders _headers; |
| 272 |
| 273 // Buffer mechanism + state |
| 274 static const int _STATE_BUILDING_HEADER = 0; |
| 275 static const int _STATE_BUILDING_RESPONSE = 1; |
| 276 static const int _STATE_ADDING_STREAM = 2; |
| 277 static const int _STATE_FINISHED = 3; |
| 278 |
| 279 final BytesBuilder _data = new BytesBuilder(); |
| 280 final List<Function> _hooksToRunBeforeEnd = []; |
| 281 final Completer _hooksAndResponseComplete = new Completer(); |
| 282 int _state = _STATE_BUILDING_HEADER; |
| 283 Future _drainFuture = null; |
| 284 |
| 285 AppengineHttpResponse(this._request, this._realResponse) { |
| 286 _headers = new AppengineHttpHeaders(this); |
| 287 } |
| 288 |
| 289 void registerHook(Function function) => _hooksToRunBeforeEnd.add(function); |
| 290 |
| 291 void add(List<int> data) { |
| 292 if (_state == _STATE_BUILDING_HEADER) { |
| 293 _state = _STATE_BUILDING_RESPONSE; |
| 294 } else if (_state == _STATE_ADDING_STREAM) { |
| 295 throw new StateError( |
| 296 'Cannot add data while addStream() has not finished'); |
| 297 } |
| 298 _enqueueData(data); |
| 299 } |
| 300 |
| 301 void addError(error, [StackTrace stackTrace]) { |
| 302 if (_state == _STATE_BUILDING_HEADER) { |
| 303 _state = _STATE_BUILDING_RESPONSE; |
| 304 } else if (_state == _STATE_ADDING_STREAM) { |
| 305 throw new StateError( |
| 306 'Cannot add data while addStream() has not finished'); |
| 307 } |
| 308 _submitData(error, stackTrace); |
| 309 } |
| 310 |
| 311 Future addStream(Stream<List<int>> stream) { |
| 312 if (_state == _STATE_ADDING_STREAM) { |
| 313 throw new StateError( |
| 314 'Cannot call addStream() before previous addStream() is done.'); |
| 315 } |
| 316 _state = _STATE_ADDING_STREAM; |
| 317 var completer = new Completer(); |
| 318 |
| 319 stream.listen((List<int> data) { |
| 320 _enqueueData(data); |
| 321 }, onError: (error, stack) { |
| 322 // NOTE: The error will be reported on the returned Future of addStream(). |
| 323 // The close()/done future will complete without an error. |
| 324 _submitData(); |
| 325 completer.completeError(error, stack); |
| 326 }, onDone: () { |
| 327 _state = _STATE_BUILDING_RESPONSE; |
| 328 completer.complete(this); |
| 329 }, cancelOnError: true); |
| 330 |
| 331 return completer.future; |
| 332 } |
| 333 |
| 334 Future flush() { |
| 335 // We have to collect all data before sending it back, so we do not support |
| 336 // flushing the output stream. |
| 337 return new Future.value(); |
| 338 } |
| 339 |
| 340 Future close() { |
| 341 _submitData(); |
| 342 return done; |
| 343 } |
| 344 |
| 345 Future get done => _hooksAndResponseComplete.future; |
| 346 |
| 347 HttpConnectionInfo get connectionInfo => _realResponse.connectionInfo; |
| 348 |
| 349 void set deadline(Duration deadline) { |
| 350 _realResponse.deadline = deadline; |
| 351 } |
| 352 |
| 353 Duration get deadline => _realResponse.deadline; |
| 354 |
| 355 void set bufferOutput(bool bufferOutput) { |
| 356 _realResponse.bufferOutput = bufferOutput; |
| 357 } |
| 358 |
| 359 bool get bufferOutput => _realResponse.bufferOutput; |
| 360 |
| 361 Future redirect(Uri location, {int status: HttpStatus.MOVED_TEMPORARILY}) { |
| 362 _ensureInHeaderBuildingState(); |
| 363 return _submitRedirect(location, status); |
| 364 } |
| 365 |
| 366 void set contentLength(int contentLength) { |
| 367 // NOTE: The state checking will be handled by [_headers]. |
| 368 _headers.contentLength = contentLength; |
| 369 } |
| 370 |
| 371 int get contentLength => _headers.contentLength; |
| 372 |
| 373 // NOTE: We have custom headers here, to override state checking, since the |
| 374 // underlying [_realResponse], doesn't know when we start buffering data. |
| 375 HttpHeaders get headers => _headers; |
| 376 |
| 377 // NOTE: The 'dart:io' implementation allows you to modify cookies after |
| 378 // writing data, so we just forward. |
| 379 List<Cookie> get cookies => _realResponse.cookies; |
| 380 |
| 381 void set statusCode(int statusCode) { |
| 382 _ensureInHeaderBuildingState(stateError: true); |
| 383 _realResponse.statusCode = statusCode; |
| 384 } |
| 385 |
| 386 int get statusCode => _realResponse.statusCode; |
| 387 |
| 388 void set persistentConnection(bool persistentConnection) { |
| 389 _ensureInHeaderBuildingState(); |
| 390 _realResponse.persistentConnection = persistentConnection; |
| 391 } |
| 392 |
| 393 bool get persistentConnection => _realResponse.persistentConnection; |
| 394 |
| 395 void set reasonPhrase(String reasonPhrase) { |
| 396 _ensureInHeaderBuildingState(stateError: true); |
| 397 _realResponse.reasonPhrase = reasonPhrase; |
| 398 } |
| 399 |
| 400 String get reasonPhrase => _realResponse.reasonPhrase; |
| 401 |
| 402 |
| 403 void set encoding(Encoding _encoding) { |
| 404 throw new StateError('HttpResponse encoding is not mutable.'); |
| 405 } |
| 406 |
| 407 Encoding get encoding => _realResponse.encoding; |
| 408 |
| 409 Future<Socket> detachSocket({bool writeHeaders: true}) { |
| 410 throw new UnsupportedError('You cannot detach the socket ' |
| 411 'from AppengineHttpResponse implementation.'); |
| 412 } |
| 413 |
| 414 Future _drain() { |
| 415 // Asynchronously detect whether we need to drain and if so drain it. |
| 416 return new Future(() { |
| 417 // If someone listens to the data, we will not drain it. |
| 418 if (_request._hasSubscriber) { |
| 419 return new Future.value(); |
| 420 } |
| 421 _request._hasSubscriber = true; |
| 422 return _request.drain().catchError((_) {}); |
| 423 }); |
| 424 } |
| 425 |
| 426 _enqueueData(List<int> data) { |
| 427 if (_state == _STATE_FINISHED) return; |
| 428 |
| 429 if (_drainFuture == null) { |
| 430 _drainFuture = _drain(); |
| 431 } |
| 432 |
| 433 _data.add(data); |
| 434 } |
| 435 |
| 436 _submitData([error, stack]) { |
| 437 if (_state == _STATE_FINISHED) return; |
| 438 _state = _STATE_FINISHED; |
| 439 |
| 440 if (_drainFuture == null) { |
| 441 _drainFuture = _drain(); |
| 442 } |
| 443 |
| 444 // Run all hooks before sending the data and closing. |
| 445 _drainFuture.then((_) { |
| 446 _runHooks().then((_) { |
| 447 if (_request.method != 'HEAD' &&_realResponse.contentLength == -1) { |
| 448 _realResponse.contentLength = _data.length; |
| 449 } |
| 450 _realResponse.add(_data.takeBytes()); |
| 451 _data.clear(); |
| 452 _realResponse.close().then((_){ |
| 453 _hooksAndResponseComplete.complete(this); |
| 454 }).catchError((error, stack) { |
| 455 _hooksAndResponseComplete.completeError(error, stack); |
| 456 }); |
| 457 }); |
| 458 }); |
| 459 } |
| 460 |
| 461 Future _submitRedirect(Uri location, int status) { |
| 462 _state = _STATE_FINISHED; |
| 463 |
| 464 if (_drainFuture == null) { |
| 465 _drainFuture = _drain(); |
| 466 } |
| 467 |
| 468 // Run all hooks before sending the redirect. |
| 469 return _drainFuture.then((_) { |
| 470 return _runHooks().then((_) { |
| 471 return _realResponse.redirect(location, status: status); |
| 472 }); |
| 473 }); |
| 474 } |
| 475 |
| 476 void _ensureInHeaderBuildingState({bool stateError: false}) { |
| 477 if (_state != _STATE_BUILDING_HEADER) { |
| 478 if (stateError) { |
| 479 throw new StateError('HTTP headers were already sent.'); |
| 480 } else { |
| 481 throw new HttpException('HTTP headers were already sent.'); |
| 482 } |
| 483 } |
| 484 } |
| 485 |
| 486 Future _runHooks() { |
| 487 // TODO: We swallow errors from the hooks here. Having an internal error |
| 488 // mechanism would be beneficial where we could report these kinds of |
| 489 // errors. |
| 490 var futures = _hooksToRunBeforeEnd.map((hook) => hook()); |
| 491 return Future.wait(futures).catchError((_) {}); |
| 492 } |
| 493 } |
| 494 |
| 495 class AppengineHttpHeaders implements HttpHeaders { |
| 496 final AppengineHttpResponse _response; |
| 497 final HttpHeaders _realHeaders; |
| 498 |
| 499 AppengineHttpHeaders(AppengineHttpResponse response) |
| 500 : _response = response, _realHeaders = response._realResponse.headers; |
| 501 |
| 502 List<String> operator [](String name) { |
| 503 // NOTE: The underlying HttpResponse from dart:io doesn't do checks, so we |
| 504 // don't do checks either here. |
| 505 return _realHeaders[name]; |
| 506 } |
| 507 |
| 508 void add(String name, Object value) { |
| 509 _response._ensureInHeaderBuildingState(); |
| 510 _realHeaders.add(name, value); |
| 511 } |
| 512 |
| 513 void set chunkedTransferEncoding(bool chunkedTransferEncoding) { |
| 514 _response._ensureInHeaderBuildingState(); |
| 515 _realHeaders.chunkedTransferEncoding = chunkedTransferEncoding; |
| 516 } |
| 517 |
| 518 bool get chunkedTransferEncoding => _realHeaders.chunkedTransferEncoding; |
| 519 |
| 520 void set contentLength(int contentLength) { |
| 521 _response._ensureInHeaderBuildingState(); |
| 522 _realHeaders.contentLength = contentLength; |
| 523 } |
| 524 |
| 525 int get contentLength => _realHeaders.contentLength; |
| 526 |
| 527 void set contentType(ContentType contentType) { |
| 528 _response._ensureInHeaderBuildingState(); |
| 529 _realHeaders.contentType = contentType; |
| 530 } |
| 531 |
| 532 ContentType get contentType => _realHeaders.contentType; |
| 533 |
| 534 void set date(DateTime date) { |
| 535 _response._ensureInHeaderBuildingState(); |
| 536 _realHeaders.date = date; |
| 537 } |
| 538 |
| 539 DateTime get date => _realHeaders.date; |
| 540 |
| 541 void set expires(DateTime expires) { |
| 542 _response._ensureInHeaderBuildingState(); |
| 543 _realHeaders.expires = expires; |
| 544 } |
| 545 |
| 546 DateTime get expires => _realHeaders.expires; |
| 547 |
| 548 void forEach(void f(String name, List<String> values)) { |
| 549 // NOTE: The underlying HttpResponse from dart:io leaks the List (which is |
| 550 // modifiable even after writing data), so we don't change that. |
| 551 _realHeaders.forEach(f); |
| 552 } |
| 553 |
| 554 void set host(String host) { |
| 555 _response._ensureInHeaderBuildingState(); |
| 556 _realHeaders.host = host; |
| 557 } |
| 558 |
| 559 String get host => _realHeaders.host; |
| 560 |
| 561 void set ifModifiedSince(DateTime ifModifiedSince) { |
| 562 _response._ensureInHeaderBuildingState(); |
| 563 _realHeaders.ifModifiedSince = ifModifiedSince; |
| 564 } |
| 565 |
| 566 DateTime get ifModifiedSince => _realHeaders.ifModifiedSince; |
| 567 |
| 568 void noFolding(String name) { |
| 569 // NOTE: The underlying HttpResponse from dart:io doesn't do checks, so we |
| 570 // don't do checks either here. |
| 571 _realHeaders.noFolding(name); |
| 572 } |
| 573 |
| 574 void set persistentConnection(bool persistentConnection) { |
| 575 _response._ensureInHeaderBuildingState(); |
| 576 _realHeaders.persistentConnection = persistentConnection; |
| 577 } |
| 578 |
| 579 bool get persistentConnection => _realHeaders.persistentConnection; |
| 580 |
| 581 void set port(int port) { |
| 582 _response._ensureInHeaderBuildingState(); |
| 583 _realHeaders.port = port; |
| 584 } |
| 585 |
| 586 int get port => _realHeaders.port; |
| 587 |
| 588 void remove(String name, Object value) { |
| 589 _response._ensureInHeaderBuildingState(); |
| 590 _realHeaders.remove(name, value); |
| 591 } |
| 592 |
| 593 void removeAll(String name) { |
| 594 _response._ensureInHeaderBuildingState(); |
| 595 _realHeaders.removeAll(name); |
| 596 } |
| 597 |
| 598 void set(String name, Object value) { |
| 599 _response._ensureInHeaderBuildingState(); |
| 600 _realHeaders.set(name, value); |
| 601 } |
| 602 |
| 603 String value(String name) => _realHeaders.value(name); |
| 604 |
| 605 void clear() { |
| 606 _response._ensureInHeaderBuildingState(); |
| 607 _realHeaders.clear(); |
| 608 } |
| 609 } |
OLD | NEW |