| OLD | NEW |
| (Empty) |
| 1 // Copyright (c) 2012, 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 // Global constants. | |
| 6 class _Const { | |
| 7 // Bytes for "HTTP/1.0". | |
| 8 static final HTTP10 = const [72, 84, 84, 80, 47, 49, 46, 48]; | |
| 9 // Bytes for "HTTP/1.1". | |
| 10 static final HTTP11 = const [72, 84, 84, 80, 47, 49, 46, 49]; | |
| 11 | |
| 12 static final END_CHUNKED = const [0x30, 13, 10, 13, 10]; | |
| 13 } | |
| 14 | |
| 15 // Frequently used character codes. | |
| 16 class _CharCode { | |
| 17 static final int HT = 9; | |
| 18 static final int LF = 10; | |
| 19 static final int CR = 13; | |
| 20 static final int SP = 32; | |
| 21 static final int COLON = 58; | |
| 22 } | |
| 23 | |
| 24 | |
| 25 // States of the HTTP parser state machine. | |
| 26 class _State { | |
| 27 static final int START = 0; | |
| 28 static final int METHOD_OR_HTTP_VERSION = 1; | |
| 29 static final int REQUEST_LINE_METHOD = 2; | |
| 30 static final int REQUEST_LINE_URI = 3; | |
| 31 static final int REQUEST_LINE_HTTP_VERSION = 4; | |
| 32 static final int REQUEST_LINE_ENDING = 5; | |
| 33 static final int RESPONSE_LINE_STATUS_CODE = 6; | |
| 34 static final int RESPONSE_LINE_REASON_PHRASE = 7; | |
| 35 static final int RESPONSE_LINE_ENDING = 8; | |
| 36 static final int HEADER_START = 9; | |
| 37 static final int HEADER_FIELD = 10; | |
| 38 static final int HEADER_VALUE_START = 11; | |
| 39 static final int HEADER_VALUE = 12; | |
| 40 static final int HEADER_VALUE_FOLDING_OR_ENDING = 13; | |
| 41 static final int HEADER_VALUE_FOLD_OR_END = 14; | |
| 42 static final int HEADER_ENDING = 15; | |
| 43 static final int CHUNK_SIZE_STARTING_CR = 16; | |
| 44 static final int CHUNK_SIZE_STARTING_LF = 17; | |
| 45 static final int CHUNK_SIZE = 18; | |
| 46 static final int CHUNK_SIZE_ENDING = 19; | |
| 47 static final int CHUNKED_BODY_DONE_CR = 20; | |
| 48 static final int CHUNKED_BODY_DONE_LF = 21; | |
| 49 static final int BODY = 22; | |
| 50 } | |
| 51 | |
| 52 | |
| 53 /** | |
| 54 * HTTP parser which parses the HTTP stream as data is supplied | |
| 55 * through the writeList method. As the data is parsed the events | |
| 56 * RequestStart | |
| 57 * UriReceived | |
| 58 * HeaderReceived | |
| 59 * HeadersComplete | |
| 60 * DataReceived | |
| 61 * DataEnd | |
| 62 * are generated. | |
| 63 * Currently only HTTP requests with Content-Length header are supported. | |
| 64 */ | |
| 65 class HttpParser { | |
| 66 HttpParser() | |
| 67 : _state = _State.START, | |
| 68 _failure = false, | |
| 69 _headerField = new StringBuffer(), | |
| 70 _headerValue = new StringBuffer(), | |
| 71 _method_or_status_code = new StringBuffer(), | |
| 72 _uri_or_reason_phrase = new StringBuffer(); | |
| 73 | |
| 74 // From RFC 2616. | |
| 75 // generic-message = start-line | |
| 76 // *(message-header CRLF) | |
| 77 // CRLF | |
| 78 // [ message-body ] | |
| 79 // start-line = Request-Line | Status-Line | |
| 80 // Request-Line = Method SP Request-URI SP HTTP-Version CRLF | |
| 81 // Status-Line = HTTP-Version SP Status-Code SP Reason-Phrase CRLF | |
| 82 // message-header = field-name ":" [ field-value ] | |
| 83 int writeList(List<int> buffer, int offset, int count) { | |
| 84 int index = offset; | |
| 85 int lastIndex = offset + count; | |
| 86 while ((index < lastIndex) && !_failure) { | |
| 87 int byte = buffer[index]; | |
| 88 switch (_state) { | |
| 89 case _State.START: | |
| 90 _contentLength = 0; | |
| 91 _keepAlive = false; | |
| 92 _chunked = false; | |
| 93 | |
| 94 if (byte == _Const.HTTP11[0]) { | |
| 95 // Start parsing HTTP method. | |
| 96 _httpVersionIndex = 1; | |
| 97 _state = _State.METHOD_OR_HTTP_VERSION; | |
| 98 } else { | |
| 99 // Start parsing method. | |
| 100 _method_or_status_code.addCharCode(byte); | |
| 101 _state = _State.REQUEST_LINE_METHOD; | |
| 102 } | |
| 103 break; | |
| 104 | |
| 105 case _State.METHOD_OR_HTTP_VERSION: | |
| 106 if (_httpVersionIndex < _Const.HTTP11.length && | |
| 107 byte == _Const.HTTP11[_httpVersionIndex]) { | |
| 108 // Continue parsing HTTP version. | |
| 109 _httpVersionIndex++; | |
| 110 } else if (_httpVersionIndex == _Const.HTTP11.length && | |
| 111 byte == _CharCode.SP) { | |
| 112 // HTTP version parsed. | |
| 113 _state = _State.RESPONSE_LINE_STATUS_CODE; | |
| 114 } else { | |
| 115 // Did not parse HTTP version. Expect method instead. | |
| 116 for (int i = 0; i < _httpVersionIndex; i++) { | |
| 117 _method_or_status_code.addCharCode(_Const.HTTP11[i]); | |
| 118 } | |
| 119 _state = _State.REQUEST_LINE_URI; | |
| 120 } | |
| 121 break; | |
| 122 | |
| 123 case _State.REQUEST_LINE_METHOD: | |
| 124 if (byte == _CharCode.SP) { | |
| 125 _state = _State.REQUEST_LINE_URI; | |
| 126 } else { | |
| 127 _method_or_status_code.addCharCode(byte); | |
| 128 } | |
| 129 break; | |
| 130 | |
| 131 case _State.REQUEST_LINE_URI: | |
| 132 if (byte == _CharCode.SP) { | |
| 133 _state = _State.REQUEST_LINE_HTTP_VERSION; | |
| 134 _httpVersionIndex = 0; | |
| 135 } else { | |
| 136 _uri_or_reason_phrase.addCharCode(byte); | |
| 137 } | |
| 138 break; | |
| 139 | |
| 140 case _State.REQUEST_LINE_HTTP_VERSION: | |
| 141 if (_httpVersionIndex < _Const.HTTP11.length) { | |
| 142 _expect(byte, _Const.HTTP11[_httpVersionIndex]); | |
| 143 _httpVersionIndex++; | |
| 144 } else { | |
| 145 _expect(byte, _CharCode.CR); | |
| 146 _state = _State.REQUEST_LINE_ENDING; | |
| 147 } | |
| 148 break; | |
| 149 | |
| 150 case _State.REQUEST_LINE_ENDING: | |
| 151 _expect(byte, _CharCode.LF); | |
| 152 if (requestStart != null) { | |
| 153 requestStart(_method_or_status_code.toString(), | |
| 154 _uri_or_reason_phrase.toString()); | |
| 155 } | |
| 156 _method_or_status_code.clear(); | |
| 157 _uri_or_reason_phrase.clear(); | |
| 158 _state = _State.HEADER_START; | |
| 159 break; | |
| 160 | |
| 161 case _State.RESPONSE_LINE_STATUS_CODE: | |
| 162 if (byte == _CharCode.SP) { | |
| 163 _state = _State.RESPONSE_LINE_REASON_PHRASE; | |
| 164 } else { | |
| 165 if (byte < 0x30 && 0x39 < byte) { | |
| 166 _failure = true; | |
| 167 } else { | |
| 168 _method_or_status_code.addCharCode(byte); | |
| 169 } | |
| 170 } | |
| 171 break; | |
| 172 | |
| 173 case _State.RESPONSE_LINE_REASON_PHRASE: | |
| 174 if (byte == _CharCode.CR) { | |
| 175 _state = _State.RESPONSE_LINE_ENDING; | |
| 176 } else { | |
| 177 _uri_or_reason_phrase.addCharCode(byte); | |
| 178 } | |
| 179 break; | |
| 180 | |
| 181 case _State.RESPONSE_LINE_ENDING: | |
| 182 _expect(byte, _CharCode.LF); | |
| 183 // TODO(sgjesse): Check for valid status code. | |
| 184 if (responseStart != null) { | |
| 185 responseStart(Math.parseInt(_method_or_status_code.toString()), | |
| 186 _uri_or_reason_phrase.toString()); | |
| 187 } | |
| 188 _method_or_status_code.clear(); | |
| 189 _uri_or_reason_phrase.clear(); | |
| 190 _state = _State.HEADER_START; | |
| 191 break; | |
| 192 | |
| 193 case _State.HEADER_START: | |
| 194 if (byte == _CharCode.CR) { | |
| 195 _state = _State.HEADER_ENDING; | |
| 196 } else { | |
| 197 // Start of new header field. | |
| 198 _headerField.addCharCode(_toLowerCase(byte)); | |
| 199 _state = _State.HEADER_FIELD; | |
| 200 } | |
| 201 break; | |
| 202 | |
| 203 case _State.HEADER_FIELD: | |
| 204 if (byte == _CharCode.COLON) { | |
| 205 _state = _State.HEADER_VALUE_START; | |
| 206 } else { | |
| 207 _headerField.addCharCode(_toLowerCase(byte)); | |
| 208 } | |
| 209 break; | |
| 210 | |
| 211 case _State.HEADER_VALUE_START: | |
| 212 if (byte != _CharCode.SP && byte != _CharCode.HT) { | |
| 213 // Start of new header value. | |
| 214 _headerValue.addCharCode(byte); | |
| 215 _state = _State.HEADER_VALUE; | |
| 216 } | |
| 217 break; | |
| 218 | |
| 219 case _State.HEADER_VALUE: | |
| 220 if (byte == _CharCode.CR) { | |
| 221 _state = _State.HEADER_VALUE_FOLDING_OR_ENDING; | |
| 222 } else { | |
| 223 _headerValue.addCharCode(byte); | |
| 224 } | |
| 225 break; | |
| 226 | |
| 227 case _State.HEADER_VALUE_FOLDING_OR_ENDING: | |
| 228 _expect(byte, _CharCode.LF); | |
| 229 _state = _State.HEADER_VALUE_FOLD_OR_END; | |
| 230 break; | |
| 231 | |
| 232 case _State.HEADER_VALUE_FOLD_OR_END: | |
| 233 if (byte == _CharCode.SP || byte == _CharCode.HT) { | |
| 234 _state = _State.HEADER_VALUE_START; | |
| 235 } else { | |
| 236 String headerField = _headerField.toString(); | |
| 237 String headerValue =_headerValue.toString(); | |
| 238 // Ignore the Content-Length header if Transfer-Encoding | |
| 239 // is chunked (RFC 2616 section 4.4) | |
| 240 if (headerField == "content-length" && !_chunked) { | |
| 241 _contentLength = Math.parseInt(headerValue); | |
| 242 } else if (headerField == "connection" && | |
| 243 headerValue == "keep-alive") { | |
| 244 _keepAlive = true; | |
| 245 } else if (headerField == "transfer-encoding" && | |
| 246 headerValue == "chunked") { | |
| 247 _chunked = true; | |
| 248 _contentLength = -1; | |
| 249 } | |
| 250 if (headerReceived != null) { | |
| 251 headerReceived(headerField, headerValue); | |
| 252 } | |
| 253 _headerField.clear(); | |
| 254 _headerValue.clear(); | |
| 255 | |
| 256 if (byte == _CharCode.CR) { | |
| 257 _state = _State.HEADER_ENDING; | |
| 258 } else { | |
| 259 // Start of new header field. | |
| 260 _headerField.addCharCode(_toLowerCase(byte)); | |
| 261 _state = _State.HEADER_FIELD; | |
| 262 } | |
| 263 } | |
| 264 break; | |
| 265 | |
| 266 case _State.HEADER_ENDING: | |
| 267 _expect(byte, _CharCode.LF); | |
| 268 if (headersComplete != null) headersComplete(); | |
| 269 | |
| 270 // If there is no data get ready to process the next request. | |
| 271 if (_chunked) { | |
| 272 _state = _State.CHUNK_SIZE; | |
| 273 _remainingContent = 0; | |
| 274 } else if (_contentLength == 0) { | |
| 275 if (dataEnd != null) dataEnd(); | |
| 276 _state = _State.START; | |
| 277 } else if (_contentLength > 0) { | |
| 278 _remainingContent = _contentLength; | |
| 279 _state = _State.BODY; | |
| 280 } else { | |
| 281 // TODO(sgjesse): Error handling. | |
| 282 } | |
| 283 break; | |
| 284 | |
| 285 case _State.CHUNK_SIZE_STARTING_CR: | |
| 286 _expect(byte, _CharCode.CR); | |
| 287 _state = _State.CHUNK_SIZE_STARTING_LF; | |
| 288 break; | |
| 289 | |
| 290 case _State.CHUNK_SIZE_STARTING_LF: | |
| 291 _expect(byte, _CharCode.LF); | |
| 292 _state = _State.CHUNK_SIZE; | |
| 293 break; | |
| 294 | |
| 295 case _State.CHUNK_SIZE: | |
| 296 if (byte == _CharCode.CR) { | |
| 297 _state = _State.CHUNK_SIZE_ENDING; | |
| 298 } else { | |
| 299 int value = _expectHexDigit(byte); | |
| 300 _remainingContent = _remainingContent * 16 + value; | |
| 301 } | |
| 302 break; | |
| 303 | |
| 304 case _State.CHUNK_SIZE_ENDING: | |
| 305 _expect(byte, _CharCode.LF); | |
| 306 if (_remainingContent > 0) { | |
| 307 _state = _State.BODY; | |
| 308 } else { | |
| 309 _state = _State.CHUNKED_BODY_DONE_CR; | |
| 310 } | |
| 311 break; | |
| 312 | |
| 313 case _State.CHUNKED_BODY_DONE_CR: | |
| 314 _expect(byte, _CharCode.CR); | |
| 315 _state = _State.CHUNKED_BODY_DONE_LF; | |
| 316 break; | |
| 317 | |
| 318 case _State.CHUNKED_BODY_DONE_LF: | |
| 319 _expect(byte, _CharCode.LF); | |
| 320 if (dataEnd != null) dataEnd(); | |
| 321 _state = _State.START; | |
| 322 break; | |
| 323 | |
| 324 case _State.BODY: | |
| 325 // The body is not handled one byte at the time but in blocks. | |
| 326 int dataAvailable = lastIndex - index; | |
| 327 ByteArray data; | |
| 328 if (dataAvailable <= _remainingContent) { | |
| 329 data = new ByteArray(dataAvailable); | |
| 330 data.setRange(0, dataAvailable, buffer, index); | |
| 331 } else { | |
| 332 data = new ByteArray(_remainingContent); | |
| 333 data.setRange(0, _remainingContent, buffer, index); | |
| 334 } | |
| 335 | |
| 336 if (dataReceived != null) dataReceived(data); | |
| 337 _remainingContent -= data.length; | |
| 338 index += data.length; | |
| 339 if (_remainingContent == 0) { | |
| 340 if (!_chunked) { | |
| 341 if (dataEnd != null) dataEnd(); | |
| 342 _state = _State.START; | |
| 343 } else { | |
| 344 _state = _State.CHUNK_SIZE_STARTING_CR; | |
| 345 } | |
| 346 } | |
| 347 | |
| 348 // Hack - as we always do index++ below. | |
| 349 index--; | |
| 350 break; | |
| 351 | |
| 352 default: | |
| 353 // Should be unreachable. | |
| 354 assert(false); | |
| 355 } | |
| 356 | |
| 357 // Move to the next byte. | |
| 358 index++; | |
| 359 } | |
| 360 | |
| 361 // Return the number of bytes parsed. | |
| 362 return index - offset; | |
| 363 } | |
| 364 | |
| 365 int get contentLength() => _contentLength; | |
| 366 bool get keepAlive() => _keepAlive; | |
| 367 | |
| 368 int _toLowerCase(int byte) { | |
| 369 final int aCode = "A".charCodeAt(0); | |
| 370 final int zCode = "Z".charCodeAt(0); | |
| 371 final int delta = "a".charCodeAt(0) - aCode; | |
| 372 return (aCode <= byte && byte <= zCode) ? byte + delta : byte; | |
| 373 } | |
| 374 | |
| 375 int _expect(int val1, int val2) { | |
| 376 if (val1 != val2) { | |
| 377 _failure = true; | |
| 378 } | |
| 379 } | |
| 380 | |
| 381 int _expectHexDigit(int byte) { | |
| 382 if (0x30 <= byte && byte <= 0x39) { | |
| 383 return byte - 0x30; // 0 - 9 | |
| 384 } else if (0x41 <= byte && byte <= 0x46) { | |
| 385 return byte - 0x41 + 10; // A - F | |
| 386 } else if (0x61 <= byte && byte <= 0x66) { | |
| 387 return byte - 0x61 + 10; // a - f | |
| 388 } else { | |
| 389 _failure = true; | |
| 390 return 0; | |
| 391 } | |
| 392 } | |
| 393 | |
| 394 int _state; | |
| 395 bool _failure; | |
| 396 int _httpVersionIndex; | |
| 397 StringBuffer _method_or_status_code; | |
| 398 StringBuffer _uri_or_reason_phrase; | |
| 399 StringBuffer _headerField; | |
| 400 StringBuffer _headerValue; | |
| 401 | |
| 402 int _contentLength; | |
| 403 bool _keepAlive; | |
| 404 bool _chunked; | |
| 405 | |
| 406 int _remainingContent; | |
| 407 | |
| 408 // Callbacks. | |
| 409 Function requestStart; | |
| 410 Function responseStart; | |
| 411 Function headerReceived; | |
| 412 Function headersComplete; | |
| 413 Function dataReceived; | |
| 414 Function dataEnd; | |
| 415 } | |
| 416 | |
| 417 | |
| 418 // Utility class for encoding a string into UTF-8 byte stream. | |
| 419 class _UTF8Encoder { | |
| 420 static List<int> encodeString(String string) { | |
| 421 int size = _encodingSize(string); | |
| 422 ByteArray result = new ByteArray(size); | |
| 423 _encodeString(string, result); | |
| 424 return result; | |
| 425 } | |
| 426 | |
| 427 static int _encodingSize(String string) => _encodeString(string, null); | |
| 428 | |
| 429 static int _encodeString(String string, List<int> buffer) { | |
| 430 int pos = 0; | |
| 431 int length = string.length; | |
| 432 for (int i = 0; i < length; i++) { | |
| 433 int additionalBytes; | |
| 434 int charCode = string.charCodeAt(i); | |
| 435 if (charCode <= 0x007F) { | |
| 436 additionalBytes = 0; | |
| 437 if (buffer != null) buffer[pos] = charCode; | |
| 438 } else if (charCode <= 0x07FF) { | |
| 439 // 110xxxxx (xxxxx is top 5 bits). | |
| 440 if (buffer != null) buffer[pos] = ((charCode >> 6) & 0x1F) | 0xC0; | |
| 441 additionalBytes = 1; | |
| 442 } else if (charCode <= 0xFFFF) { | |
| 443 // 1110xxxx (xxxx is top 4 bits) | |
| 444 if (buffer != null) buffer[pos] = ((charCode >> 12) & 0x0F)| 0xE0; | |
| 445 additionalBytes = 2; | |
| 446 } else { | |
| 447 // 11110xxx (xxx is top 3 bits) | |
| 448 if (buffer != null) buffer[pos] = ((charCode >> 18) & 0x07) | 0xF0; | |
| 449 additionalBytes = 3; | |
| 450 } | |
| 451 pos++; | |
| 452 if (buffer != null) { | |
| 453 for (int i = additionalBytes; i > 0; i--) { | |
| 454 // 10xxxxxx (xxxxxx is next 6 bits from the top). | |
| 455 buffer[pos++] = ((charCode >> (6 * (i - 1))) & 0x3F) | 0x80; | |
| 456 } | |
| 457 } else { | |
| 458 pos += additionalBytes; | |
| 459 } | |
| 460 } | |
| 461 return pos; | |
| 462 } | |
| 463 } | |
| 464 | |
| 465 | |
| 466 class _HttpRequestResponseBase { | |
| 467 _HttpRequestResponseBase(_HttpConnectionBase this._httpConnection) | |
| 468 : _contentLength = -1, | |
| 469 _keepAlive = false, | |
| 470 _headers = new Map(); | |
| 471 | |
| 472 int get contentLength() => _contentLength; | |
| 473 bool get keepAlive() => _keepAlive; | |
| 474 | |
| 475 void _setHeader(String name, String value) { | |
| 476 _headers[name] = value; | |
| 477 } | |
| 478 | |
| 479 bool _write(List<int> data, bool copyBuffer) { | |
| 480 bool allWritten = true; | |
| 481 if (data.length > 0) { | |
| 482 if (_contentLength < 0) { | |
| 483 // Write chunk size if transfer encoding is chunked. | |
| 484 _writeHexString(data.length); | |
| 485 _writeCRLF(); | |
| 486 _httpConnection.outputStream.write(data, copyBuffer); | |
| 487 allWritten = _writeCRLF(); | |
| 488 } else { | |
| 489 allWritten = _httpConnection.outputStream.write(data, copyBuffer); | |
| 490 } | |
| 491 } | |
| 492 return allWritten; | |
| 493 } | |
| 494 | |
| 495 bool _writeList(List<int> data, int offset, int count) { | |
| 496 bool allWritten = true; | |
| 497 if (count > 0) { | |
| 498 if (_contentLength < 0) { | |
| 499 // Write chunk size if transfer encoding is chunked. | |
| 500 _writeHexString(count); | |
| 501 _writeCRLF(); | |
| 502 _httpConnection.outputStream.writeFrom(data, offset, count); | |
| 503 allWritten = _writeCRLF(); | |
| 504 } else { | |
| 505 allWritten = _httpConnection.outputStream.writeFrom(data, offset, count)
; | |
| 506 } | |
| 507 } | |
| 508 return allWritten; | |
| 509 } | |
| 510 | |
| 511 bool _writeString(String string) { | |
| 512 bool allWritten = true; | |
| 513 if (string.length > 0) { | |
| 514 // Encode as UTF-8 and write data. | |
| 515 List<int> data = _UTF8Encoder.encodeString(string); | |
| 516 allWritten = _writeList(data, 0, data.length); | |
| 517 } | |
| 518 return allWritten; | |
| 519 } | |
| 520 | |
| 521 bool _writeDone() { | |
| 522 bool allWritten = true; | |
| 523 if (_contentLength < 0) { | |
| 524 // Terminate the content if transfer encoding is chunked. | |
| 525 allWritten = _httpConnection.outputStream.write(_Const.END_CHUNKED); | |
| 526 } | |
| 527 return allWritten; | |
| 528 } | |
| 529 | |
| 530 bool _writeHeaders() { | |
| 531 List<int> data; | |
| 532 | |
| 533 // Format headers. | |
| 534 _headers.forEach((String name, String value) { | |
| 535 data = name.charCodes(); | |
| 536 _httpConnection.outputStream.write(data); | |
| 537 data = ": ".charCodes(); | |
| 538 _httpConnection.outputStream.write(data); | |
| 539 data = value.charCodes(); | |
| 540 _httpConnection.outputStream.write(data); | |
| 541 _writeCRLF(); | |
| 542 }); | |
| 543 // Terminate header. | |
| 544 return _writeCRLF(); | |
| 545 } | |
| 546 | |
| 547 bool _writeHexString(int x) { | |
| 548 final List<int> hexDigits = [0x30, 0x31, 0x32, 0x33, 0x34, | |
| 549 0x35, 0x36, 0x37, 0x38, 0x39, | |
| 550 0x41, 0x42, 0x43, 0x44, 0x45, 0x46]; | |
| 551 ByteArray hex = new ByteArray(10); | |
| 552 int index = hex.length; | |
| 553 while (x > 0) { | |
| 554 index--; | |
| 555 hex[index] = hexDigits[x % 16]; | |
| 556 x = x >> 4; | |
| 557 } | |
| 558 return _httpConnection.outputStream.writeFrom(hex, index, hex.length - index
); | |
| 559 } | |
| 560 | |
| 561 bool _writeCRLF() { | |
| 562 final CRLF = const [_CharCode.CR, _CharCode.LF]; | |
| 563 return _httpConnection.outputStream.write(CRLF); | |
| 564 } | |
| 565 | |
| 566 bool _writeSP() { | |
| 567 final SP = const [_CharCode.SP]; | |
| 568 return _httpConnection.outputStream.write(SP); | |
| 569 } | |
| 570 | |
| 571 _HttpConnectionBase _httpConnection; | |
| 572 Map<String, String> _headers; | |
| 573 | |
| 574 // Length of the content body. If this is set to -1 (default value) | |
| 575 // when starting to send data chunked transfer encoding will be | |
| 576 // used. | |
| 577 int _contentLength; | |
| 578 bool _keepAlive; | |
| 579 } | |
| 580 | |
| 581 | |
| 582 // Parsed HTTP request providing information on the HTTP headers. | |
| 583 class _HttpRequest extends _HttpRequestResponseBase implements HttpRequest { | |
| 584 _HttpRequest(_HttpConnection connection) : super(connection); | |
| 585 | |
| 586 String get method() => _method; | |
| 587 String get uri() => _uri; | |
| 588 String get path() => _path; | |
| 589 Map get headers() => _headers; | |
| 590 String get queryString() => _queryString; | |
| 591 Map get queryParameters() => _queryParameters; | |
| 592 | |
| 593 InputStream get inputStream() { | |
| 594 if (_inputStream == null) { | |
| 595 _inputStream = new _HttpInputStream(this); | |
| 596 } | |
| 597 return _inputStream; | |
| 598 } | |
| 599 | |
| 600 void _requestStartHandler(String method, String uri) { | |
| 601 _method = method; | |
| 602 _uri = uri; | |
| 603 _parseRequestUri(uri); | |
| 604 } | |
| 605 | |
| 606 void _headerReceivedHandler(String name, String value) { | |
| 607 _setHeader(name, value); | |
| 608 } | |
| 609 | |
| 610 void _headersCompleteHandler() { | |
| 611 // Prepare for receiving data. | |
| 612 _buffer = new _BufferList(); | |
| 613 } | |
| 614 | |
| 615 void _dataReceivedHandler(List<int> data) { | |
| 616 _buffer.add(data); | |
| 617 if (_inputStream != null) _inputStream._dataReceived(); | |
| 618 } | |
| 619 | |
| 620 void _dataEndHandler() { | |
| 621 if (_inputStream != null) _inputStream._closeReceived(); | |
| 622 } | |
| 623 | |
| 624 // Escaped characters in uri are expected to have been parsed. | |
| 625 void _parseRequestUri(String uri) { | |
| 626 int position; | |
| 627 position = uri.indexOf("?", 0); | |
| 628 if (position == -1) { | |
| 629 _path = HttpUtil.decodeUrlEncodedString(_uri); | |
| 630 _queryString = null; | |
| 631 _queryParameters = new Map(); | |
| 632 } else { | |
| 633 _path = HttpUtil.decodeUrlEncodedString(_uri.substring(0, position)); | |
| 634 _queryString = _uri.substring(position + 1); | |
| 635 _queryParameters = HttpUtil.splitQueryString(_queryString); | |
| 636 } | |
| 637 } | |
| 638 | |
| 639 // Delegate functions for the HttpInputStream implementation. | |
| 640 int _streamAvailable() { | |
| 641 return _buffer.length; | |
| 642 } | |
| 643 | |
| 644 List<int> _streamRead(int bytesToRead) { | |
| 645 return _buffer.readBytes(bytesToRead); | |
| 646 } | |
| 647 | |
| 648 int _streamReadInto(List<int> buffer, int offset, int len) { | |
| 649 List<int> data = _buffer.readBytes(len); | |
| 650 buffer.setRange(offset, data.length, data); | |
| 651 } | |
| 652 | |
| 653 String _method; | |
| 654 String _uri; | |
| 655 String _path; | |
| 656 String _queryString; | |
| 657 Map<String, String> _queryParameters; | |
| 658 _HttpInputStream _inputStream; | |
| 659 _BufferList _buffer; | |
| 660 } | |
| 661 | |
| 662 | |
| 663 // HTTP response object for sending a HTTP response. | |
| 664 class _HttpResponse extends _HttpRequestResponseBase implements HttpResponse { | |
| 665 static final int START = 0; | |
| 666 static final int HEADERS_SENT = 1; | |
| 667 static final int DONE = 2; | |
| 668 | |
| 669 _HttpResponse(_HttpConnection httpConnection) | |
| 670 : super(httpConnection), | |
| 671 statusCode = HttpStatus.OK, | |
| 672 _state = START; | |
| 673 | |
| 674 void set contentLength(int contentLength) { | |
| 675 if (_outputStream != null) return new HttpException("Header already sent"); | |
| 676 _contentLength = contentLength; | |
| 677 } | |
| 678 void set keepAlive(bool keepAlive) { | |
| 679 if (_outputStream != null) return new HttpException("Header already sent"); | |
| 680 _keepAlive = keepAlive; | |
| 681 } | |
| 682 | |
| 683 // Set a header on the response. NOTE: If the same header is set | |
| 684 // more than once only the last one will be part of the response. | |
| 685 void setHeader(String name, String value) { | |
| 686 if (_outputStream != null) return new HttpException("Header already sent"); | |
| 687 _setHeader(name, value); | |
| 688 } | |
| 689 | |
| 690 OutputStream get outputStream() { | |
| 691 if (_state == DONE) throw new HttpException("Response closed"); | |
| 692 if (_outputStream == null) { | |
| 693 // Ensure that headers are written. | |
| 694 if (_state == START) { | |
| 695 _writeHeader(); | |
| 696 } | |
| 697 _outputStream = new _HttpOutputStream(this); | |
| 698 } | |
| 699 return _outputStream; | |
| 700 } | |
| 701 | |
| 702 bool writeString(String string) { | |
| 703 // Invoke the output stream getter to make sure the header is sent. | |
| 704 outputStream; | |
| 705 return _writeString(string); | |
| 706 } | |
| 707 | |
| 708 // Delegate functions for the HttpOutputStream implementation. | |
| 709 bool _streamWrite(List<int> buffer, bool copyBuffer) { | |
| 710 return _write(buffer, copyBuffer); | |
| 711 } | |
| 712 | |
| 713 bool _streamWriteFrom(List<int> buffer, int offset, int len) { | |
| 714 return _writeList(buffer, offset, len); | |
| 715 } | |
| 716 | |
| 717 void _streamClose() { | |
| 718 _state = DONE; | |
| 719 // Stop tracking no pending write events. | |
| 720 _httpConnection.outputStream.noPendingWriteHandler = null; | |
| 721 // Ensure that any trailing data is written. | |
| 722 _writeDone(); | |
| 723 // If the connection is closing then close the output stream to | |
| 724 // fully close the socket. | |
| 725 if (_httpConnection._closing) { | |
| 726 _httpConnection.outputStream.close(); | |
| 727 } | |
| 728 } | |
| 729 | |
| 730 void _streamSetNoPendingWriteHandler(callback()) { | |
| 731 if (_state != DONE) { | |
| 732 _httpConnection.outputStream.noPendingWriteHandler = callback; | |
| 733 } | |
| 734 } | |
| 735 | |
| 736 void _streamSetCloseHandler(callback()) { | |
| 737 // TODO(sgjesse): Handle this. | |
| 738 } | |
| 739 | |
| 740 void _streamSetErrorHandler(callback()) { | |
| 741 // TODO(sgjesse): Handle this. | |
| 742 } | |
| 743 | |
| 744 String _findReasonPhrase(int statusCode) { | |
| 745 if (reasonPhrase != null) { | |
| 746 return reasonPhrase; | |
| 747 } | |
| 748 | |
| 749 switch (statusCode) { | |
| 750 case HttpStatus.CONTINUE: return "Continue"; | |
| 751 case HttpStatus.SWITCHING_PROTOCOLS: return "Switching Protocols"; | |
| 752 case HttpStatus.OK: return "OK"; | |
| 753 case HttpStatus.CREATED: return "Created"; | |
| 754 case HttpStatus.ACCEPTED: return "Accepted"; | |
| 755 case HttpStatus.NON_AUTHORITATIVE_INFORMATION: | |
| 756 return "Non-Authoritative Information"; | |
| 757 case HttpStatus.NO_CONTENT: return "No Content"; | |
| 758 case HttpStatus.RESET_CONTENT: return "Reset Content"; | |
| 759 case HttpStatus.PARTIAL_CONTENT: return "Partial Content"; | |
| 760 case HttpStatus.MULTIPLE_CHOICES: return "Multiple Choices"; | |
| 761 case HttpStatus.MOVED_PERMANENTLY: return "Moved Permanently"; | |
| 762 case HttpStatus.FOUND: return "Found"; | |
| 763 case HttpStatus.SEE_OTHER: return "See Other"; | |
| 764 case HttpStatus.NOT_MODIFIED: return "Not Modified"; | |
| 765 case HttpStatus.USE_PROXY: return "Use Proxy"; | |
| 766 case HttpStatus.TEMPORARY_REDIRECT: return "Temporary Redirect"; | |
| 767 case HttpStatus.BAD_REQUEST: return "Bad Request"; | |
| 768 case HttpStatus.UNAUTHORIZED: return "Unauthorized"; | |
| 769 case HttpStatus.PAYMENT_REQUIRED: return "Payment Required"; | |
| 770 case HttpStatus.FORBIDDEN: return "Forbidden"; | |
| 771 case HttpStatus.NOT_FOUND: return "Not Found"; | |
| 772 case HttpStatus.METHOD_NOT_ALLOWED: return "Method Not Allowed"; | |
| 773 case HttpStatus.NOT_ACCEPTABLE: return "Not Acceptable"; | |
| 774 case HttpStatus.PROXY_AUTHENTICATION_REQUIRED: | |
| 775 return "Proxy Authentication Required"; | |
| 776 case HttpStatus.REQUEST_TIMEOUT: return "Request Time-out"; | |
| 777 case HttpStatus.CONFLICT: return "Conflict"; | |
| 778 case HttpStatus.GONE: return "Gone"; | |
| 779 case HttpStatus.LENGTH_REQUIRED: return "Length Required"; | |
| 780 case HttpStatus.PRECONDITION_FAILED: return "Precondition Failed"; | |
| 781 case HttpStatus.REQUEST_ENTITY_TOO_LARGE: | |
| 782 return "Request Entity Too Large"; | |
| 783 case HttpStatus.REQUEST_URI_TOO_LONG: return "Request-URI Too Large"; | |
| 784 case HttpStatus.UNSUPPORTED_MEDIA_TYPE: return "Unsupported Media Type"; | |
| 785 case HttpStatus.REQUESTED_RANGE_NOT_SATISFIABLE: | |
| 786 return "Requested range not satisfiable"; | |
| 787 case HttpStatus.EXPECTATION_FAILED: return "Expectation Failed"; | |
| 788 case HttpStatus.INTERNAL_SERVER_ERROR: return "Internal Server Error"; | |
| 789 case HttpStatus.NOT_IMPLEMENTED: return "Not Implemented"; | |
| 790 case HttpStatus.BAD_GATEWAY: return "Bad Gateway"; | |
| 791 case HttpStatus.SERVICE_UNAVAILABLE: return "Service Unavailable"; | |
| 792 case HttpStatus.GATEWAY_TIMEOUT: return "Gateway Time-out"; | |
| 793 case HttpStatus.HTTP_VERSION_NOT_SUPPORTED: | |
| 794 return "Http Version not supported"; | |
| 795 default: return "Status " + statusCode.toString(); | |
| 796 } | |
| 797 } | |
| 798 | |
| 799 bool _writeHeader() { | |
| 800 List<int> data; | |
| 801 OutputStream stream = _httpConnection.outputStream; | |
| 802 | |
| 803 // Write status line. | |
| 804 stream.write(_Const.HTTP11); | |
| 805 _writeSP(); | |
| 806 data = statusCode.toString().charCodes(); | |
| 807 stream.write(data); | |
| 808 _writeSP(); | |
| 809 data = _findReasonPhrase(statusCode).charCodes(); | |
| 810 stream.write(data); | |
| 811 _writeCRLF(); | |
| 812 | |
| 813 // Determine the value of the "Connection" header | |
| 814 // based on the keep alive state. | |
| 815 setHeader("Connection", keepAlive ? "keep-alive" : "close"); | |
| 816 // Determine the value of the "Transfer-Encoding" header based on | |
| 817 // whether the content length is known. | |
| 818 if (_contentLength >= 0) { | |
| 819 setHeader("Content-Length", _contentLength.toString()); | |
| 820 } else { | |
| 821 setHeader("Transfer-Encoding", "chunked"); | |
| 822 } | |
| 823 | |
| 824 // Write headers. | |
| 825 bool allWritten = _writeHeaders(); | |
| 826 _state = HEADERS_SENT; | |
| 827 return allWritten; | |
| 828 } | |
| 829 | |
| 830 // Response status code. | |
| 831 int statusCode; | |
| 832 String reasonPhrase; | |
| 833 _HttpOutputStream _outputStream; | |
| 834 int _state; | |
| 835 } | |
| 836 | |
| 837 | |
| 838 class _HttpInputStream extends _BaseDataInputStream implements InputStream { | |
| 839 _HttpInputStream(_HttpRequestResponseBase this._requestOrResponse) { | |
| 840 _checkScheduleCallbacks(); | |
| 841 } | |
| 842 | |
| 843 int available() { | |
| 844 return _requestOrResponse._streamAvailable(); | |
| 845 } | |
| 846 | |
| 847 void pipe(OutputStream output, [bool close = true]) { | |
| 848 _pipe(this, output, close: close); | |
| 849 } | |
| 850 | |
| 851 List<int> _read(int bytesToRead) { | |
| 852 List<int> result = _requestOrResponse._streamRead(bytesToRead); | |
| 853 _checkScheduleCallbacks(); | |
| 854 return result; | |
| 855 } | |
| 856 | |
| 857 int _readInto(List<int> buffer, int offset, int len) { | |
| 858 int result = _requestOrResponse._streamReadInto(buffer, offset, len); | |
| 859 _checkScheduleCallbacks(); | |
| 860 return result; | |
| 861 } | |
| 862 | |
| 863 void _close() { | |
| 864 // TODO(sgjesse): Handle this. | |
| 865 } | |
| 866 | |
| 867 void _dataReceived() { | |
| 868 super._dataReceived(); | |
| 869 } | |
| 870 | |
| 871 _HttpRequestResponseBase _requestOrResponse; | |
| 872 } | |
| 873 | |
| 874 | |
| 875 class _HttpOutputStream implements OutputStream { | |
| 876 _HttpOutputStream(_HttpRequestResponseBase this._requestOrResponse); | |
| 877 | |
| 878 bool write(List<int> buffer, [bool copyBuffer = true]) { | |
| 879 return _requestOrResponse._streamWrite(buffer, copyBuffer); | |
| 880 } | |
| 881 | |
| 882 bool writeFrom(List<int> buffer, [int offset = 0, int len]) { | |
| 883 return _requestOrResponse._streamWriteFrom(buffer, offset, len); | |
| 884 } | |
| 885 | |
| 886 void close() { | |
| 887 _requestOrResponse._streamClose(); | |
| 888 } | |
| 889 | |
| 890 void destroy() { | |
| 891 throw "Not implemented"; | |
| 892 } | |
| 893 | |
| 894 void set noPendingWriteHandler(void callback()) { | |
| 895 _requestOrResponse._streamSetNoPendingWriteHandler(callback); | |
| 896 } | |
| 897 | |
| 898 void set closeHandler(void callback()) { | |
| 899 _requestOrResponse._streamSetCloseHandler(callback); | |
| 900 } | |
| 901 | |
| 902 void set errorHandler(void callback()) { | |
| 903 _requestOrResponse._streamSetErrorHandler(callback); | |
| 904 } | |
| 905 | |
| 906 _HttpRequestResponseBase _requestOrResponse; | |
| 907 } | |
| 908 | |
| 909 | |
| 910 class _HttpConnectionBase { | |
| 911 _HttpConnectionBase() : _sendBuffers = new Queue(), | |
| 912 _httpParser = new HttpParser(); | |
| 913 | |
| 914 void _connectionEstablished(Socket socket) { | |
| 915 _socket = socket; | |
| 916 // Register handler for socket events. | |
| 917 _socket.dataHandler = _dataHandler; | |
| 918 _socket.closeHandler = _closeHandler; | |
| 919 _socket.errorHandler = _errorHandler; | |
| 920 } | |
| 921 | |
| 922 OutputStream get outputStream() { | |
| 923 return _socket.outputStream; | |
| 924 } | |
| 925 | |
| 926 void _dataHandler() { | |
| 927 int available = _socket.available(); | |
| 928 if (available == 0) { | |
| 929 return; | |
| 930 } | |
| 931 | |
| 932 ByteArray buffer = new ByteArray(available); | |
| 933 int bytesRead = _socket.readList(buffer, 0, available); | |
| 934 if (bytesRead > 0) { | |
| 935 int parsed = _httpParser.writeList(buffer, 0, bytesRead); | |
| 936 if (parsed != bytesRead) { | |
| 937 print("Failed to parse HTTP data $parsed $bytesRead"); | |
| 938 _socket.close(); | |
| 939 } | |
| 940 } | |
| 941 } | |
| 942 | |
| 943 void _closeHandler() { | |
| 944 // Client closed socket for writing. Socket should still be open | |
| 945 // for writing the response. | |
| 946 _closing = true; | |
| 947 if (_disconnectHandlerCallback != null) _disconnectHandlerCallback(); | |
| 948 } | |
| 949 | |
| 950 void _errorHandler() { | |
| 951 // If an error occours, treat the socket as closed. | |
| 952 _closeHandler(); | |
| 953 if (_errorHandlerCallback != null) { | |
| 954 _errorHandlerCallback("Connection closed while sending data to client."); | |
| 955 } | |
| 956 } | |
| 957 | |
| 958 void set disconnectHandler(void callback()) { | |
| 959 _disconnectHandlerCallback = callback; | |
| 960 } | |
| 961 | |
| 962 void set errorHandler(void callback(String errorMessage)) { | |
| 963 _errorHandlerCallback = callback; | |
| 964 } | |
| 965 | |
| 966 Socket _socket; | |
| 967 bool _closing = false; // Is the socket closed by the client? | |
| 968 HttpParser _httpParser; | |
| 969 | |
| 970 Queue _sendBuffers; | |
| 971 | |
| 972 Function _disconnectHandlerCallback; | |
| 973 Function _errorHandlerCallback; | |
| 974 } | |
| 975 | |
| 976 | |
| 977 // HTTP server connection over a socket. | |
| 978 class _HttpConnection extends _HttpConnectionBase { | |
| 979 _HttpConnection() { | |
| 980 // Register HTTP parser callbacks. | |
| 981 _httpParser.requestStart = | |
| 982 (method, uri) => _requestStartHandler(method, uri); | |
| 983 _httpParser.responseStart = | |
| 984 (statusCode, reasonPhrase) => | |
| 985 _responseStartHandler(statusCode, reasonPhrase); | |
| 986 _httpParser.headerReceived = | |
| 987 (name, value) => _headerReceivedHandler(name, value); | |
| 988 _httpParser.headersComplete = () => _headersCompleteHandler(); | |
| 989 _httpParser.dataReceived = (data) => _dataReceivedHandler(data); | |
| 990 _httpParser.dataEnd = () => _dataEndHandler(); | |
| 991 } | |
| 992 | |
| 993 void _requestStartHandler(String method, String uri) { | |
| 994 // Create new request and response objects for this request. | |
| 995 _request = new _HttpRequest(this); | |
| 996 _response = new _HttpResponse(this); | |
| 997 _request._requestStartHandler(method, uri); | |
| 998 } | |
| 999 | |
| 1000 void _responseStartHandler(int statusCode, String reasonPhrase) { | |
| 1001 // TODO(sgjesse): Error handling. | |
| 1002 } | |
| 1003 | |
| 1004 void _headerReceivedHandler(String name, String value) { | |
| 1005 _request._headerReceivedHandler(name, value); | |
| 1006 } | |
| 1007 | |
| 1008 void _headersCompleteHandler() { | |
| 1009 _request._headersCompleteHandler(); | |
| 1010 _response.keepAlive = _httpParser.keepAlive; | |
| 1011 if (requestReceived != null) { | |
| 1012 requestReceived(_request, _response); | |
| 1013 } | |
| 1014 } | |
| 1015 | |
| 1016 void _dataReceivedHandler(List<int> data) { | |
| 1017 _request._dataReceivedHandler(data); | |
| 1018 } | |
| 1019 | |
| 1020 void _dataEndHandler() { | |
| 1021 _request._dataEndHandler(); | |
| 1022 } | |
| 1023 | |
| 1024 HttpRequest _request; | |
| 1025 HttpResponse _response; | |
| 1026 | |
| 1027 // Callbacks. | |
| 1028 var requestReceived; | |
| 1029 } | |
| 1030 | |
| 1031 | |
| 1032 // HTTP server waiting for socket connections. The connections are | |
| 1033 // managed by the server and as requests are received the request. | |
| 1034 class _HttpServer implements HttpServer { | |
| 1035 _HttpServer () : _debugTrace = false; | |
| 1036 | |
| 1037 void listen(String host, int port, [int backlog = 5]) { | |
| 1038 | |
| 1039 void connectionHandler(Socket socket) { | |
| 1040 // Accept the client connection. | |
| 1041 _HttpConnection connection = new _HttpConnection(); | |
| 1042 connection._connectionEstablished(socket); | |
| 1043 connection.requestReceived = _requestHandler; | |
| 1044 _connections.add(connection); | |
| 1045 if (_debugTrace) { | |
| 1046 print("New connection (total ${_connections.length} connections)"); | |
| 1047 } | |
| 1048 void disconnectHandler() { | |
| 1049 for (int i = 0; i < _connections.length; i++) { | |
| 1050 if (_connections[i] == connection) { | |
| 1051 _connections.removeRange(i, 1); | |
| 1052 break; | |
| 1053 } | |
| 1054 } | |
| 1055 if (_debugTrace) { | |
| 1056 print("Closed connection (total ${_connections.length} connections)"); | |
| 1057 } | |
| 1058 } | |
| 1059 connection.disconnectHandler = disconnectHandler; | |
| 1060 void errorHandler(String errorMessage) { | |
| 1061 if (_errorHandler != null) _errorHandler(errorMessage); | |
| 1062 } | |
| 1063 connection.errorHandler = errorHandler; | |
| 1064 } | |
| 1065 | |
| 1066 // TODO(ajohnsen): Use Set once Socket is Hashable. | |
| 1067 _connections = new List<_HttpConnection>(); | |
| 1068 _server = new ServerSocket(host, port, backlog); | |
| 1069 _server.connectionHandler = connectionHandler; | |
| 1070 } | |
| 1071 | |
| 1072 void close() => _server.close(); | |
| 1073 int get port() => _server.port; | |
| 1074 | |
| 1075 void set errorHandler(void handler(String errorMessage)) { | |
| 1076 _errorHandler = handler; | |
| 1077 } | |
| 1078 | |
| 1079 void set requestHandler(void handler(HttpRequest, HttpResponse)) { | |
| 1080 _requestHandler = handler; | |
| 1081 } | |
| 1082 | |
| 1083 ServerSocket _server; // The server listen socket. | |
| 1084 List<_HttpConnection> _connections; // List of currently connected clients. | |
| 1085 Function _requestHandler; | |
| 1086 Function _errorHandler; | |
| 1087 bool _debugTrace; | |
| 1088 } | |
| 1089 | |
| 1090 | |
| 1091 class _HttpClientRequest | |
| 1092 extends _HttpRequestResponseBase implements HttpClientRequest { | |
| 1093 static final int START = 0; | |
| 1094 static final int HEADERS_SENT = 1; | |
| 1095 static final int DONE = 2; | |
| 1096 | |
| 1097 _HttpClientRequest(String this._method, | |
| 1098 String this._uri, | |
| 1099 _HttpClientConnection connection) | |
| 1100 : super(connection), | |
| 1101 _state = START { | |
| 1102 _connection = connection; | |
| 1103 // Default GET requests to have no content. | |
| 1104 if (_method == "GET") { | |
| 1105 _contentLength = 0; | |
| 1106 } | |
| 1107 } | |
| 1108 | |
| 1109 void set contentLength(int contentLength) => _contentLength = contentLength; | |
| 1110 void set keepAlive(bool keepAlive) => _keepAlive = keepAlive; | |
| 1111 int get statusCode() { return _statusCode; } | |
| 1112 String get reasonPhrase() { return _reasonPhrase; } | |
| 1113 | |
| 1114 void setHeader(String name, String value) { | |
| 1115 _setHeader(name, value); | |
| 1116 } | |
| 1117 | |
| 1118 bool writeString(String string) { | |
| 1119 outputStream; | |
| 1120 return _writeString(string); | |
| 1121 } | |
| 1122 | |
| 1123 OutputStream get outputStream() { | |
| 1124 if (_state == DONE) throw new HttpException("Request closed"); | |
| 1125 if (_outputStream == null) { | |
| 1126 // Ensure that headers are written. | |
| 1127 if (_state == START) { | |
| 1128 _writeHeader(); | |
| 1129 } | |
| 1130 _outputStream = new _HttpOutputStream(this); | |
| 1131 } | |
| 1132 return _outputStream; | |
| 1133 } | |
| 1134 | |
| 1135 // Delegate functions for the HttpOutputStream implementation. | |
| 1136 bool _streamWrite(List<int> buffer, bool copyBuffer) { | |
| 1137 return _write(buffer, copyBuffer); | |
| 1138 } | |
| 1139 | |
| 1140 bool _streamWriteFrom(List<int> buffer, int offset, int len) { | |
| 1141 return _writeList(buffer, offset, len); | |
| 1142 } | |
| 1143 | |
| 1144 void _streamClose() { | |
| 1145 _state = DONE; | |
| 1146 // Stop tracking no pending write events. | |
| 1147 _httpConnection.outputStream.noPendingWriteHandler = null; | |
| 1148 // Ensure that any trailing data is written. | |
| 1149 _writeDone(); | |
| 1150 // If the connection is closing then close the output stream to | |
| 1151 // fully close the socket. | |
| 1152 if (_httpConnection._closing) { | |
| 1153 _httpConnection.outputStream.close(); | |
| 1154 } | |
| 1155 } | |
| 1156 | |
| 1157 void _streamSetNoPendingWriteHandler(callback()) { | |
| 1158 if (_state != DONE) { | |
| 1159 _httpConnection.outputStream.noPendingWriteHandler = callback; | |
| 1160 } | |
| 1161 } | |
| 1162 | |
| 1163 void _streamSetCloseHandler(callback()) { | |
| 1164 // TODO(sgjesse): Handle this. | |
| 1165 } | |
| 1166 | |
| 1167 void _streamSetErrorHandler(callback()) { | |
| 1168 // TODO(sgjesse): Handle this. | |
| 1169 } | |
| 1170 | |
| 1171 void _writeHeader() { | |
| 1172 List<int> data; | |
| 1173 OutputStream stream = _httpConnection.outputStream; | |
| 1174 | |
| 1175 // Write request line. | |
| 1176 data = _method.toString().charCodes(); | |
| 1177 stream.write(data); | |
| 1178 _writeSP(); | |
| 1179 data = _uri.toString().charCodes(); | |
| 1180 stream.write(data); | |
| 1181 _writeSP(); | |
| 1182 stream.write(_Const.HTTP11); | |
| 1183 _writeCRLF(); | |
| 1184 | |
| 1185 // Determine the value of the "Connection" header | |
| 1186 // based on the keep alive state. | |
| 1187 setHeader("Connection", keepAlive ? "keep-alive" : "close"); | |
| 1188 // Determine the value of the "Transfer-Encoding" header based on | |
| 1189 // whether the content length is known. | |
| 1190 if (_contentLength >= 0) { | |
| 1191 setHeader("Content-Length", _contentLength.toString()); | |
| 1192 } else { | |
| 1193 setHeader("Transfer-Encoding", "chunked"); | |
| 1194 } | |
| 1195 | |
| 1196 // Write headers. | |
| 1197 _writeHeaders(); | |
| 1198 _state = HEADERS_SENT; | |
| 1199 } | |
| 1200 | |
| 1201 String _method; | |
| 1202 String _uri; | |
| 1203 _HttpClientConnection _connection; | |
| 1204 _HttpOutputStream _outputStream; | |
| 1205 int _state; | |
| 1206 } | |
| 1207 | |
| 1208 | |
| 1209 class _HttpClientResponse | |
| 1210 extends _HttpRequestResponseBase implements HttpClientResponse { | |
| 1211 _HttpClientResponse(_HttpClientConnection connection) | |
| 1212 : super(connection) { | |
| 1213 _connection = connection; | |
| 1214 } | |
| 1215 | |
| 1216 int get statusCode() { return _statusCode; } | |
| 1217 int get reasonPhrase() { return _reasonPhrase; } | |
| 1218 Map get headers() => _headers; | |
| 1219 | |
| 1220 InputStream get inputStream() { | |
| 1221 if (_inputStream == null) { | |
| 1222 _inputStream = new _HttpInputStream(this); | |
| 1223 } | |
| 1224 return _inputStream; | |
| 1225 } | |
| 1226 | |
| 1227 void _requestStartHandler(String method, String uri) { | |
| 1228 // TODO(sgjesse): Error handling | |
| 1229 } | |
| 1230 | |
| 1231 void _responseStartHandler(int statusCode, String reasonPhrase) { | |
| 1232 _statusCode = statusCode; | |
| 1233 _reasonPhrase = reasonPhrase; | |
| 1234 } | |
| 1235 | |
| 1236 void _headerReceivedHandler(String name, String value) { | |
| 1237 _setHeader(name, value); | |
| 1238 } | |
| 1239 | |
| 1240 void _headersCompleteHandler() { | |
| 1241 _buffer = new _BufferList(); | |
| 1242 if (_connection._responseHandler != null) { | |
| 1243 _connection._responseHandler(this); | |
| 1244 } | |
| 1245 } | |
| 1246 | |
| 1247 void _dataReceivedHandler(List<int> data) { | |
| 1248 _buffer.add(data); | |
| 1249 if (_inputStream != null) _inputStream._dataReceived(); | |
| 1250 } | |
| 1251 | |
| 1252 void _dataEndHandler() { | |
| 1253 if (_inputStream != null) _inputStream._closeReceived(); | |
| 1254 } | |
| 1255 | |
| 1256 // Delegate functions for the HttpInputStream implementation. | |
| 1257 int _streamAvailable() { | |
| 1258 return _buffer.length; | |
| 1259 } | |
| 1260 | |
| 1261 List<int> _streamRead(int bytesToRead) { | |
| 1262 return _buffer.readBytes(bytesToRead); | |
| 1263 } | |
| 1264 | |
| 1265 int _streamReadInto(List<int> buffer, int offset, int len) { | |
| 1266 List<int> data = _buffer.readBytes(len); | |
| 1267 buffer.setRange(offset, data.length, data); | |
| 1268 return data.length; | |
| 1269 } | |
| 1270 | |
| 1271 int _statusCode; | |
| 1272 String _reasonPhrase; | |
| 1273 | |
| 1274 _HttpClientConnection _connection; | |
| 1275 _HttpInputStream _inputStream; | |
| 1276 _BufferList _buffer; | |
| 1277 } | |
| 1278 | |
| 1279 | |
| 1280 class _HttpClientConnection | |
| 1281 extends _HttpConnectionBase implements HttpClientConnection { | |
| 1282 _HttpClientConnection(_HttpClient this._client); | |
| 1283 | |
| 1284 void _connectionEstablished(_SocketConnection socketConn) { | |
| 1285 super._connectionEstablished(socketConn._socket); | |
| 1286 _socketConn = socketConn; | |
| 1287 // Register HTTP parser callbacks. | |
| 1288 _httpParser.requestStart = | |
| 1289 (method, uri) => _requestStartHandler(method, uri); | |
| 1290 _httpParser.responseStart = | |
| 1291 (statusCode, reasonPhrase) => | |
| 1292 _responseStartHandler(statusCode, reasonPhrase); | |
| 1293 _httpParser.headerReceived = | |
| 1294 (name, value) => _headerReceivedHandler(name, value); | |
| 1295 _httpParser.headersComplete = () => _headersCompleteHandler(); | |
| 1296 _httpParser.dataReceived = (data) => _dataReceivedHandler(data); | |
| 1297 _httpParser.dataEnd = () => _dataEndHandler(); | |
| 1298 } | |
| 1299 | |
| 1300 HttpClientRequest open(String method, String uri) { | |
| 1301 _request = new _HttpClientRequest(method, uri, this); | |
| 1302 _request.keepAlive = true; | |
| 1303 _response = new _HttpClientResponse(this); | |
| 1304 return _request; | |
| 1305 } | |
| 1306 | |
| 1307 void _requestStartHandler(String method, String uri) { | |
| 1308 // TODO(sgjesse): Error handling. | |
| 1309 } | |
| 1310 | |
| 1311 void _responseStartHandler(int statusCode, String reasonPhrase) { | |
| 1312 _response._responseStartHandler(statusCode, reasonPhrase); | |
| 1313 } | |
| 1314 | |
| 1315 void _headerReceivedHandler(String name, String value) { | |
| 1316 _response._headerReceivedHandler(name, value); | |
| 1317 } | |
| 1318 | |
| 1319 void _headersCompleteHandler() { | |
| 1320 _response._headersCompleteHandler(); | |
| 1321 } | |
| 1322 | |
| 1323 void _dataReceivedHandler(List<int> data) { | |
| 1324 _response._dataReceivedHandler(data); | |
| 1325 } | |
| 1326 | |
| 1327 void _dataEndHandler() { | |
| 1328 if (_response.headers["connection"] == "close") { | |
| 1329 _socket.close(); | |
| 1330 } else { | |
| 1331 _client._returnSocketConnection(_socketConn); | |
| 1332 _socket = null; | |
| 1333 _socketConn = null; | |
| 1334 } | |
| 1335 _response._dataEndHandler(); | |
| 1336 } | |
| 1337 | |
| 1338 void set requestHandler(void handler(HttpClientRequest request)) { | |
| 1339 _requestHandler = handler; | |
| 1340 } | |
| 1341 | |
| 1342 void set responseHandler(void handler(HttpClientResponse response)) { | |
| 1343 _responseHandler = handler; | |
| 1344 } | |
| 1345 | |
| 1346 Function _requestHandler; | |
| 1347 Function _responseHandler; | |
| 1348 | |
| 1349 _HttpClient _client; | |
| 1350 _SocketConnection _socketConn; | |
| 1351 HttpClientRequest _request; | |
| 1352 HttpClientResponse _response; | |
| 1353 | |
| 1354 // Callbacks. | |
| 1355 var requestReceived; | |
| 1356 | |
| 1357 } | |
| 1358 | |
| 1359 | |
| 1360 // Class for holding keep-alive sockets in the cache for the HTTP | |
| 1361 // client together with the connection information. | |
| 1362 class _SocketConnection { | |
| 1363 _SocketConnection(String this._host, | |
| 1364 int this._port, | |
| 1365 Socket this._socket); | |
| 1366 | |
| 1367 void _markReturned() { | |
| 1368 _socket.dataHandler = null; | |
| 1369 _socket.closeHandler = null; | |
| 1370 _socket.errorHandler = null; | |
| 1371 _returnTime = new Date.now(); | |
| 1372 } | |
| 1373 | |
| 1374 Duration _idleTime(Date now) => now.difference(_returnTime); | |
| 1375 | |
| 1376 String _host; | |
| 1377 int _port; | |
| 1378 Socket _socket; | |
| 1379 Date _returnTime; | |
| 1380 } | |
| 1381 | |
| 1382 | |
| 1383 class _HttpClient implements HttpClient { | |
| 1384 static final int DEFAULT_EVICTION_TIMEOUT = 60000; | |
| 1385 | |
| 1386 _HttpClient() : _openSockets = new Map(), _shutdown = false; | |
| 1387 | |
| 1388 HttpClientConnection open( | |
| 1389 String method, String host, int port, String path) { | |
| 1390 if (_shutdown) throw new HttpException("HttpClient shutdown"); | |
| 1391 return _prepareHttpClientConnection(host, port, method, path); | |
| 1392 } | |
| 1393 | |
| 1394 HttpClientConnection get(String host, int port, String path) { | |
| 1395 return open("GET", host, port, path); | |
| 1396 } | |
| 1397 | |
| 1398 HttpClientConnection post(String host, int port, String path) { | |
| 1399 return open("POST", host, port, path); | |
| 1400 } | |
| 1401 | |
| 1402 void shutdown() { | |
| 1403 _openSockets.forEach( | |
| 1404 void _(String key, Queue<_SocketConnection> connections) { | |
| 1405 while (!connections.isEmpty()) { | |
| 1406 var socketConn = connections.removeFirst(); | |
| 1407 socketConn._socket.close(); | |
| 1408 } | |
| 1409 }); | |
| 1410 if (_evictionTimer != null) { | |
| 1411 _evictionTimer.cancel(); | |
| 1412 } | |
| 1413 _shutdown = true; | |
| 1414 } | |
| 1415 | |
| 1416 String _connectionKey(String host, int port) { | |
| 1417 return "$host:$port"; | |
| 1418 } | |
| 1419 | |
| 1420 HttpClientConnection _prepareHttpClientConnection( | |
| 1421 String host, int port, String method, String path) { | |
| 1422 | |
| 1423 void _connectionOpened(_SocketConnection socketConn, | |
| 1424 _HttpClientConnection connection) { | |
| 1425 connection._connectionEstablished(socketConn); | |
| 1426 HttpClientRequest request = connection.open(method, path); | |
| 1427 if (connection._requestHandler != null) { | |
| 1428 connection._requestHandler(request); | |
| 1429 } else { | |
| 1430 request.outputStream.close(); | |
| 1431 } | |
| 1432 } | |
| 1433 | |
| 1434 _HttpClientConnection connection = new _HttpClientConnection(this); | |
| 1435 | |
| 1436 // If there are active connections for this key get the first one | |
| 1437 // otherwise create a new one. | |
| 1438 Queue socketConnections = _openSockets[_connectionKey(host, port)]; | |
| 1439 if (socketConnections == null || socketConnections.isEmpty()) { | |
| 1440 Socket socket = new Socket(host, port); | |
| 1441 socket.connectHandler = () { | |
| 1442 socket.errorHandler = null; | |
| 1443 _SocketConnection socketConn = | |
| 1444 new _SocketConnection(host, port, socket); | |
| 1445 _connectionOpened(socketConn, connection); | |
| 1446 }; | |
| 1447 socket.errorHandler = () { | |
| 1448 if (_errorHandler !== null) { | |
| 1449 _errorHandler(HttpStatus.NETWORK_CONNECT_TIMEOUT_ERROR); | |
| 1450 } | |
| 1451 }; | |
| 1452 } else { | |
| 1453 _SocketConnection socketConn = socketConnections.removeFirst(); | |
| 1454 new Timer((ignored) => _connectionOpened(socketConn, connection), 0); | |
| 1455 | |
| 1456 // Get rid of eviction timer if there are no more active connections. | |
| 1457 if (socketConnections.isEmpty()) { | |
| 1458 _evictionTimer.cancel(); | |
| 1459 _evictionTimer = null; | |
| 1460 } | |
| 1461 } | |
| 1462 | |
| 1463 return connection; | |
| 1464 } | |
| 1465 | |
| 1466 void _returnSocketConnection(_SocketConnection socketConn) { | |
| 1467 // If the HTTP client is beeing shutdown don't return the connection. | |
| 1468 if (_shutdown) { | |
| 1469 socketConn._socket.close(); | |
| 1470 return; | |
| 1471 }; | |
| 1472 | |
| 1473 String key = _connectionKey(socketConn._host, socketConn._port); | |
| 1474 | |
| 1475 // Get or create the connection list for this key. | |
| 1476 Queue sockets = _openSockets[key]; | |
| 1477 if (sockets == null) { | |
| 1478 sockets = new Queue(); | |
| 1479 _openSockets[key] = sockets; | |
| 1480 } | |
| 1481 | |
| 1482 // If there is currently no eviction timer start one. | |
| 1483 if (_evictionTimer == null) { | |
| 1484 void _handleEviction(Timer timer) { | |
| 1485 Date now = new Date.now(); | |
| 1486 _openSockets.forEach( | |
| 1487 void _(String key, Queue<_SocketConnection> connections) { | |
| 1488 // As returned connections are added at the head of the | |
| 1489 // list remove from the tail. | |
| 1490 while (!connections.isEmpty()) { | |
| 1491 _SocketConnection socketConn = connections.last(); | |
| 1492 if (socketConn._idleTime(now).inMilliseconds > | |
| 1493 DEFAULT_EVICTION_TIMEOUT) { | |
| 1494 connections.removeLast(); | |
| 1495 } else { | |
| 1496 break; | |
| 1497 } | |
| 1498 } | |
| 1499 }); | |
| 1500 } | |
| 1501 _evictionTimer = new Timer.repeating(_handleEviction, 10000); | |
| 1502 } | |
| 1503 | |
| 1504 // Return connection. | |
| 1505 sockets.addFirst(socketConn); | |
| 1506 socketConn._markReturned(); | |
| 1507 } | |
| 1508 | |
| 1509 void set errorHandler(void callback(int status)) { | |
| 1510 _errorHandler = callback; | |
| 1511 } | |
| 1512 | |
| 1513 Function _openHandler; | |
| 1514 Function _errorHandler; | |
| 1515 Map<String, Queue<_SocketConnection>> _openSockets; | |
| 1516 Timer _evictionTimer; | |
| 1517 bool _shutdown; // Has this HTTP client been shutdown? | |
| 1518 } | |
| 1519 | |
| 1520 | |
| 1521 class HttpUtil { | |
| 1522 static String decodeUrlEncodedString(String urlEncoded) { | |
| 1523 void invalidEscape() { | |
| 1524 // TODO(sgjesse): Handle the error. | |
| 1525 print("Invalid escape code."); | |
| 1526 } | |
| 1527 | |
| 1528 StringBuffer result = new StringBuffer(); | |
| 1529 for (int ii = 0; urlEncoded.length > ii; ++ii) { | |
| 1530 if ('+' == urlEncoded[ii]) { | |
| 1531 result.add(' '); | |
| 1532 } else if ('%' == urlEncoded[ii] && | |
| 1533 urlEncoded.length - 2 > ii) { | |
| 1534 try { | |
| 1535 int charCode = | |
| 1536 Math.parseInt('0x' + urlEncoded.substring(ii + 1, ii + 3)); | |
| 1537 if (charCode <= 0x7f) { | |
| 1538 result.add(new String.fromCharCodes([charCode])); | |
| 1539 ii += 2; | |
| 1540 } else { | |
| 1541 invalidEscape(); | |
| 1542 return ''; | |
| 1543 } | |
| 1544 } catch (BadNumberFormatException ignored) { | |
| 1545 invalidEscape(); | |
| 1546 return ''; | |
| 1547 } | |
| 1548 } else { | |
| 1549 result.add(urlEncoded[ii]); | |
| 1550 } | |
| 1551 } | |
| 1552 return result.toString(); | |
| 1553 } | |
| 1554 | |
| 1555 static Map<String, String> splitQueryString(String queryString) { | |
| 1556 Map<String, String> result = new Map<String, String>(); | |
| 1557 int currentPosition = 0; | |
| 1558 while (currentPosition < queryString.length) { | |
| 1559 int position = queryString.indexOf("=", currentPosition); | |
| 1560 if (position == -1) { | |
| 1561 break; | |
| 1562 } | |
| 1563 String name = queryString.substring(currentPosition, position); | |
| 1564 currentPosition = position + 1; | |
| 1565 position = queryString.indexOf("&", currentPosition); | |
| 1566 String value; | |
| 1567 if (position == -1) { | |
| 1568 value = queryString.substring(currentPosition); | |
| 1569 currentPosition = queryString.length; | |
| 1570 } else { | |
| 1571 value = queryString.substring(currentPosition, position); | |
| 1572 currentPosition = position + 1; | |
| 1573 } | |
| 1574 result[HttpUtil.decodeUrlEncodedString(name)] = | |
| 1575 HttpUtil.decodeUrlEncodedString(value); | |
| 1576 } | |
| 1577 return result; | |
| 1578 } | |
| 1579 } | |
| OLD | NEW |