Compare commits

..

224 Commits

Author SHA1 Message Date
John Hiesey cd697901d1 3.2.0 2021-04-14 12:39:41 -07:00
John Hiesey 934f110003 Re-enable Safari 11 tests 2021-04-14 12:06:02 -07:00
John Hiesey 9e359bbc00 Fix sauce matrix 2021-04-14 11:52:46 -07:00
John Hiesey ca7bd49cd6 Disable tests on Safari 11 2021-04-14 11:49:56 -07:00
John Hiesey 8998953efd Reset timers on abort as well 2021-04-14 11:08:39 -07:00
John Hiesey e53322af5a Merge pull request #121 from jhiesey/socket-timeout
Socket timeout
2021-04-13 22:30:11 -07:00
John Hiesey 80ef3ca4ed Improve test reliability and bump deps 2021-04-13 22:29:31 -07:00
John Hiesey a2ba46d0ab Also support opts.timeout 2021-04-13 21:05:59 -07:00
John Hiesey 2c44eeb142 Update readme 2021-04-13 17:12:33 -07:00
John Hiesey 2572062eae Add timeout test 2021-04-13 16:27:08 -07:00
John Hiesey 681541d968 Emit error on destroy(err) 2021-04-13 16:23:47 -07:00
John Hiesey 54f5c2caae Add ClientRequest.setTimeout 2021-04-13 14:55:08 -07:00
Feross Aboukhadijeh e84db22d08 3.1.1 2020-05-05 10:37:01 -07:00
Feross Aboukhadijeh c05cfbbae8 bump all deps 2020-05-05 09:57:13 -07:00
Feross Aboukhadijeh 0657afd6ce tape@5 2020-05-05 09:57:13 -07:00
Feross Aboukhadijeh 36f13468b4 airtap@3 2020-05-05 09:57:13 -07:00
Feross Aboukhadijeh 0e1f2f30fc Merge pull request #112 from jhiesey/unused-var 2020-05-05 09:54:37 -07:00
Feross Aboukhadijeh f20f3cc30a Merge pull request #114 from cusspvz/patch-1 2020-05-05 09:54:24 -07:00
José Moreira 8320c894b9 fix: typo for the mode prefer-streaming at the readme.md file 2019-10-30 23:20:40 +00:00
Feross Aboukhadijeh 79f7465418 Remove unused variable 2019-08-12 18:16:38 -07:00
Feross Aboukhadijeh a38d9ab491 3.1.0 2019-08-06 19:18:49 -07:00
Feross Aboukhadijeh b81de11325 airtap: test last two chrome, firefox, edge
Speeds up tests considerably

Note to @jhiesey: feel free to change this to test more browsers if you want to, but for the evergreen browsers (chrome, firefox, and soon edge) it seems fine to just test the last two versions since users shouldn't be on any other version. Up to you though :)
2019-08-06 19:18:36 -07:00
Feross Aboukhadijeh 7092c3c95f Delete package-lock.json 2019-08-06 19:02:49 -07:00
Feross Aboukhadijeh 2e66ab4b92 airtap: test android up to latest version 2019-08-06 18:31:11 -07:00
Feross Aboukhadijeh 4c264353b6 airtap: only test iphone 10.3+ (older was removed) 2019-08-06 18:30:52 -07:00
Feross Aboukhadijeh 5a834dcf16 airtap: Only test last 5 versions of chrome, firefox, edge 2019-08-06 18:30:38 -07:00
Feross Aboukhadijeh 7df5e4e076 airtap@2 2019-08-06 18:27:35 -07:00
Feross Aboukhadijeh 8346bacf94 ignore package-lock.json 2019-08-06 18:17:47 -07:00
Feross Aboukhadijeh 72bd014322 Merge pull request #103 from reggi/master
Add test folder to .npmignore
2019-08-06 18:16:12 -07:00
Thomas Reggi cb2aad2640 add yml files 2019-01-04 00:34:01 -05:00
Thomas Reggi 9d51d294ac ignore test 2019-01-04 00:32:09 -05:00
John Hiesey e60ce5fb99 Replace erroneous test disable 2018-10-02 21:45:10 -07:00
John Hiesey 9390b50f0d Bump readable-stream version again 2018-10-02 13:58:23 -07:00
John Hiesey 01e6e83bf9 Remove to-arraybuffer dependency 2018-10-02 10:32:58 -07:00
John Hiesey 00bed7dc11 Bump readable-stream version 2018-10-02 10:32:01 -07:00
John Hiesey 6bdba6c1ae Merge pull request #95 from hugomrdias/patch-1
Delete ie8-polyfill.js
2018-09-27 02:53:47 -07:00
Hugo Dias faeca6025b Delete ie8-polyfill.js 2018-09-27 10:52:01 +01:00
John Hiesey 00a4d3a5a8 Drop support for old Android browsers 2018-09-24 18:17:21 -07:00
John Hiesey 270011d074 Readme cleanup 2018-09-24 16:59:04 -07:00
John Hiesey 6f81d63645 Re-enable IE11 tests
Going to wait for the readable-stream fix before releasing
v3.0.0
2018-09-24 15:45:22 -07:00
John Hiesey bd3cd9b7ce Upgrade readable-stream to v3.0.3
This breaks IE11, but that should be fixed soon.
2018-09-24 15:05:14 -07:00
John Hiesey 4c2fd5a65d Re-disable android 7 tests 2018-09-24 14:39:04 -07:00
John Hiesey 0887fcf0b4 Major cleanup to remove IE10 and earlier support 2018-09-24 14:09:42 -07:00
John Hiesey 2a8467dd84 Merge pull request #93 from hugomrdias/fix/post
fix: fix >150mb bodies from crashing
2018-09-24 11:14:57 -07:00
Hugo Dias 983a99ca6c fix: fix >150mb bodies from crashing 2018-09-24 14:32:28 +01:00
John Hiesey 718f5127ec Update airtap 2018-08-22 22:46:23 -07:00
John Hiesey 65aed45b2d 2.8.3 2018-06-01 17:52:26 -07:00
John Hiesey 05dd964945 Merge pull request #89 from wmhilton/patch-1
Allow setting User-Agent Header
2018-06-01 17:33:29 -07:00
William Hilton 4f73bb1a3c Allow setting User-Agent Header
The User-Agent header has not been forbidden since Firefox 43 (see https://developer.mozilla.org/en-US/docs/Glossary/Forbidden_header_name).

> Note: The User-Agent header is no longer forbidden, as per spec — see forbidden header name list (this was implemented in Firefox 43) — it can now be set in a Fetch Headers object, or via XHR setRequestHeader().

I've had success setting it in Firefox and Edge for `fetch` and it should work for XMLHttpRequest as well but I'm too lazy to look up how to use XMLHttpRequest. I haven't had any luck setting User-Agent it in Chrome yet - may be a Chrome bug - but I'm not seeing any warnings in the terminal when I use it either, so I don't think allowing it will cause any spurious warnings.
2018-06-01 16:47:30 -04:00
John Hiesey 663f88323c 2.8.2 2018-05-09 19:26:31 -07:00
John Hiesey 12a0b3fd2a npm update 2018-05-09 16:37:22 -07:00
John Hiesey 520cc451e8 Clear fetch timeout after request finishes
Instead of right when response is created, which misses timeouts
that occur later
2018-05-08 21:52:39 -07:00
John Hiesey 5c3b919dab Fix edge tests 2018-05-08 21:52:05 -07:00
John Hiesey 5bc3a7205e Add (disabled) test for timeout after success
Unfortunately, reasonable timeout lengths tend to cause
spurious failures, so this test shouldn't be enabled
normally.
2018-05-08 20:37:36 -07:00
John Hiesey 80cbe3c5fe Improve fetch error handling 2018-05-08 17:36:01 -07:00
John Hiesey c3ab1f7bf0 Update package-lock.json 2018-05-08 16:18:22 -07:00
John Hiesey 98095e85eb Bump airtap version 2018-05-08 16:18:11 -07:00
John Hiesey 6547015c3d Also clear fetch timeout on error 2018-05-08 16:13:41 -07:00
John Hiesey 4b8abf52f3 Fetch clearTimeout fixup 2018-05-08 16:07:46 -07:00
John Hiesey 472ed03ea7 Merge pull request #87 from luozhang002/fetchmode
fix:add clearTimeout issue#85
2018-05-08 16:04:48 -07:00
罗章 16ec2ee996 fix:add clearTimeout 2018-05-03 13:24:32 +08:00
John Hiesey 8cf284cd4b 2.8.1 2018-03-14 13:27:52 -07:00
John Hiesey 709d1acc3d Stop trying to run tests on iOS 8
It seems that this is no longer supported by Sauce Labs
2018-03-14 13:14:14 -07:00
John Hiesey f800fad249 Fix incorrect definition of http.IncomingMessage
Also add test for top-level http properties, plus a stub http.globalAgent
Fixes #81
2018-03-14 13:08:47 -07:00
John Hiesey 920dd3cd48 Bump airtap version 2018-02-26 22:50:33 -08:00
John Hiesey 433d1d5e46 Stop testing safari 7 since sauce labs gives errors 2018-02-26 22:50:01 -08:00
John Hiesey 86bfe01159 Re-enable webworker test with workaround 2018-02-26 17:43:26 -08:00
John Hiesey 8188419852 Merge pull request #84 from jhiesey/airtap
Switch from zuul to airtap for running browser tests
2018-02-22 00:56:32 -08:00
John Hiesey aa4882c7c6 Fix running browser tests locally with airtap 2018-02-22 00:40:33 -08:00
John Hiesey d62864934a Disable android 7 tests due to airtap issue
Upstream issue: https://github.com/airtap/browsers/issues/3
2018-02-22 00:29:58 -08:00
John Hiesey e875fc72b0 Switch from zuul to airtap for tests 2018-02-21 22:22:04 -08:00
John Hiesey 38ffb38660 2.8.0 2018-01-15 02:48:35 -08:00
John Hiesey 16c1baf178 Add Uint8Array support for body
This will be used if available. Fixes #57 and #80
2018-01-15 01:16:31 -08:00
John Hiesey 0ecf194fa4 Remove test support for older browsers
Opera (all versions) and Safari < 7 aren't supported by sauce labs
anymore, so don't test them.
2018-01-15 01:16:31 -08:00
John Hiesey d4631b211c Change timeout behavior
Add opts.requestTimeout. Fixes #66
2018-01-15 01:16:31 -08:00
John Hiesey bf580d32dd Expose ClientRequest and IncomingMessage constructors
For compatibility with node. Fixes #76 and #81.
2018-01-15 01:16:31 -08:00
John Hiesey 80f74c2107 Update dependencies and add npm-shrinkwrap.json 2018-01-15 01:16:31 -08:00
John Hiesey c2e1444670 Update documentation 2018-01-15 01:03:26 -08:00
John Hiesey 4fefc64d39 Don't emit error after destroyed 2018-01-15 01:03:26 -08:00
John Hiesey f94670b6b4 Add proper abort/timeout support for fetch
Fixes #26, finally!
2018-01-15 01:03:26 -08:00
John Hiesey 46c35150fc Implement backpressure
Uses WritableStream, which is still quite new.
2018-01-15 01:03:07 -08:00
John Hiesey c09a871543 2.7.2 2017-06-12 13:21:48 -07:00
John Hiesey 856a572341 fix tests on Safari 10.0 on iOS 10.3 2017-06-12 13:01:14 -07:00
John Hiesey 4175a65117 Merge pull request #78 from EsrefDurna/master
updated package.json
2017-06-12 11:51:51 -07:00
Esref Durna e2bfae151c updated package cookie-parser from 1.3.5 to 1.4.3
Npm link :  https://www.npmjs.com/package/cookie-parser
 Repository link : https://github.com/expressjs/cookie-parser/releases
2017-06-12 01:24:11 +00:00
John Hiesey 8eab4b3e5f 2.7.1 2017-05-08 17:44:33 -07:00
John Hiesey 46923f37d9 Fix Android tests
Not sure how this ever worked. This seems to have been broken for quite a while.
2017-05-08 17:31:38 -07:00
John Hiesey 2771d95bde Merge pull request #77 from GusCaplan/patch-1
fix getHeader when header isn't available
2017-04-28 18:34:51 -07:00
Gus Caplan 7c2e61fac0 fix getHeader when header isn't available 2017-04-26 02:05:49 -05:00
John Hiesey 1dc9ea32f5 2.7.0 2017-04-03 23:46:27 -07:00
John Hiesey e8ed108767 Sauce labs no longer supports tests on android < 4.4 2017-04-03 23:45:47 -07:00
John Hiesey 4cf6771177 Disable broken timeout test
timeout support is fairly broken; see #66
2017-04-03 23:43:08 -07:00
John Hiesey e54de15542 Handle arrays in request headers and improve header handling
Passing an array as a header value now works as in node.
Header handling with multiple values for the same header is also
tested, and mime type test updated for safari 10.1

Fixes #73
2017-04-03 22:25:57 -07:00
John Hiesey 943f5bc89d use blacklist instead of whitelist for methods with bodies
Instead of only permitting bodies on certain methods, only
prohibit them when the browser would give an error (with fetch)
or ignore them (with XHR). This allows bodies with custom
methods and is closer to the behavior of http in node.

Fixes #69
2017-04-03 20:25:20 -07:00
John Hiesey 1c25d6f3a3 bump dependency versions 2017-04-03 20:20:47 -07:00
John Hiesey b63537f11d 2.6.3 2017-01-19 03:26:43 -08:00
John Hiesey 11d14fc642 Revert unnecessary part of previous change 2017-01-17 20:23:25 -08:00
John Hiesey 8e1f534a8a Fix Edge fetch errors 2017-01-17 18:39:22 -08:00
John Hiesey c1417c5dfa 2.6.2 2017-01-16 02:00:08 -08:00
John Hiesey 8df10e0d65 Avoid passing undefined to xhr.send()
Provides a more elegant fix to #67
2017-01-16 01:57:32 -08:00
John Hiesey 5ee30ad5cc Fix timeout test failure 2017-01-16 00:35:53 -08:00
John Hiesey 9321e56abd 2.6.1 2017-01-11 23:44:27 -08:00
John Hiesey 881e7c8f3d Merge pull request #65 from jhiesey/csp-xhr-fix
Avoid xhr request to example.com if fetch is supported
2017-01-11 22:44:35 -08:00
Feross Aboukhadijeh 43761f2799 Avoid xhr request to example.com if fetch is supported
For: https://github.com/brave/browser-laptop/issues/5981
2017-01-10 22:17:09 -08:00
John Hiesey 70f8c7d0b7 2.6.0 2017-01-06 01:58:52 -10:00
John Hiesey f304ef13a2 bump builtin-status-codes version 2017-01-06 01:24:15 -10:00
John Hiesey e8ef98b286 style consistency 2017-01-06 01:04:07 -10:00
John Hiesey 3af33f862f Merge pull request #61 from dominictarr/master
if xhr.open throws, then no xhr is allowed
2017-01-06 03:00:46 -08:00
John Hiesey 138d6c1d29 Merge pull request #62 from rlisagor/master
Catching fetch `read()` errors
2017-01-06 02:57:53 -08:00
Roman Lisagor f97dd885ea Catching fetch read() errors
Errors that occur while reading from the ReadableStream that is returned as part of a `fetch` request were not handled. This change propagates these errors up to the `ClientRequest`.
2017-01-04 14:41:01 -08:00
John Hiesey f9fb1c724c Merge pull request #55 from node-influx/master
Support timeout option in http.request
2016-12-27 13:44:03 -08:00
Dominic Tarr 1b077bf249 if xhr.open throws, then no xhr is allowed 2016-12-09 21:04:31 +01:00
John Hiesey e342cdbb10 Merge pull request #59 from mikeal/service-worker
Adding support for Service Workers.
2016-11-16 19:22:00 -08:00
Mikeal Rogers 0a04a09f68 Adding support for Service Workers. 2016-11-16 19:18:19 -08:00
John Hiesey 8c07b21b82 2.5.0 2016-11-03 22:16:57 -07:00
John Hiesey 662b9626be Merge pull request #58 from XiaoningLiu/master
Add payload for Microsoft Azure Storage MERGE method
2016-11-03 22:15:19 -07:00
Xiaoning Liu abdc5573da Add payload for Microsoft Azure Storage MERGE method 2016-11-04 11:35:18 +08:00
John Hiesey 525957277a 2.4.1 2016-10-28 01:22:01 -07:00
John Hiesey 4e7103a5a9 Fix feature testing on IE
The previous commit broke IE<=9, since xhr doesn't work cross-domain.
When XDomainRequest is available, use the current page instead of
example.com
2016-10-27 20:03:14 -07:00
John Hiesey 34aa684789 Always use example.com for feature testing
Previously, there was some logic to use the page host
when possible, but it was problematic still (e.g. #53).

Just use example.com in all cases instead. Since the
request is never sent, this shouldn't cause extra
network traffic (except perhaps a dns lookup).

Fixes #53
2016-10-27 19:05:25 -07:00
John Hiesey 11ac890744 Merge pull request #56 from dandv/patch-1
Fix capitalization
2016-10-27 18:48:23 -07:00
John Hiesey 33587da68d Merge pull request #54 from ecoslado/edge-silent-error
Replaces symbol iterator with foreach function
2016-10-27 18:46:09 -07:00
ecoslado 5cf0329683 Revert "Adding comments"
This reverts commit 0cc4549d4e.
2016-10-24 17:12:14 +02:00
ecoslado 0cc4549d4e Adding comments 2016-10-19 10:23:34 +02:00
Dan Dascalescu 64e2d89062 Fix capitalization 2016-10-17 22:29:23 -07:00
Connor Peet 57d0ab245f Support timeout option in http.request 2016-10-17 19:17:44 -07:00
ecoslado 4b9bbe04b2 Replaces symbol iterator with foreach function 2016-10-10 15:31:35 +02:00
John Hiesey 92500c71be 2.4.0 2016-09-08 16:38:51 -07:00
John Hiesey 05c6f4394c Merge pull request #52 from jhiesey/bump-test-node-ver
Bump travis node version
2016-09-08 15:57:23 -07:00
John Hiesey 9ed7614aed Bump travis node version
Trying to debug tests that fail only on travis
2016-09-08 15:48:33 -07:00
John Hiesey bad84ecb3a Merge pull request #51 from alexjeffburke/allow-disabling-fetch
Allow using a mode to disable the use of fetch
2016-09-08 14:37:38 -07:00
John Hiesey 70b928bbb9 Fix arraybuffer for phantomjs
This should be an equivalent fix to #42
2016-09-08 14:35:22 -07:00
Alex J Burke 53bf1d38e2 Allow using a mode to disable the use of fetch and add a test for the case. 2016-09-01 07:48:38 +01:00
John Hiesey ac250a9b04 Bump minimum iphone version 2016-07-27 21:34:08 -07:00
John Hiesey 83f7b7ef32 Bump zuul version 2016-07-27 21:33:37 -07:00
John Hiesey f3df5d7ace Bump iphone and android minimum tested versions
Sauce Labs no longer supports ios 6. Also correct
the floating point version format.
2016-07-27 18:33:37 -07:00
John Hiesey 345149d708 2.3.1 2016-07-27 18:15:02 -07:00
John Hiesey 9c18855feb Check for ReadableStream instead of ReadableByteStream
ReadableByteStream was removed from the spec and from
Chrome 51. Fixes #46
2016-07-27 14:24:51 -07:00
John Hiesey ff601a9eff 2.3.0 2016-04-28 00:58:52 -07:00
John Hiesey e046049394 Merge pull request #41 from jhiesey/readable-stream
use readable-stream
2016-04-28 00:58:19 -07:00
Feross Aboukhadijeh 333b0437a9 sauce labs: fix edge version 2016-04-28 01:13:17 +02:00
Feross Aboukhadijeh 0552e1e4f5 use readable-stream
For a consistent version of the stream package.

Also, I believe this will help fix
https://github.com/feross/webtorrent/issues/771
2016-04-28 00:55:58 +02:00
John Hiesey 1fbfc4264a Merge pull request #40 from jhiesey/response-url
Expose response url
2016-04-20 13:10:11 -07:00
John Hiesey a0c6d49ba4 Expose response url
Fixes #39
2016-04-20 12:53:54 -07:00
John Hiesey 1d3fca03fa Remove dead line 2016-04-20 01:24:31 -07:00
John Hiesey 5e25ed2f65 2.2.1 2016-03-25 18:53:56 -07:00
John Hiesey b339f4afa9 Fix microsoft edge versions for newer zuul 2016-03-24 03:09:09 -07:00
John Hiesey 4b1419cff7 2.2.0 2016-03-01 15:25:22 -08:00
John Hiesey c9183cd2ad Style fixup 2016-03-01 15:05:17 -08:00
John Hiesey 0f1f0c6365 Merge pull request #37 from sportle/feature/set-cookie-array
Set-Cookie headers should be arrays
2016-03-01 15:04:03 -08:00
Phil Larson 581a4d12da Set-Cookie headers should be arrays 2016-03-01 10:15:19 -08:00
Feross Aboukhadijeh 945083bd1d 2.1.1 2016-02-15 21:32:47 -08:00
Feross Aboukhadijeh 2054c18374 sauce labs: add microsoft edge 2016-02-15 20:54:38 -08:00
Feross Aboukhadijeh 218fd54ef0 builtin-status-codes@2 2016-02-15 20:51:06 -08:00
John Hiesey 9d24d5510a 2.1.0 2016-01-13 05:46:14 +01:00
John Hiesey ff68130d89 Add missing "bugs" and "homepage" entries to package.json 2016-01-13 05:10:57 +01:00
John Hiesey 6bab67d17a Use to-arraybuffer instead of manual conversion
This fixes some more edge cases when converting from a Buffer to
an ArrayBuffer.

Additionally, compared v2.0.5, this avoids an unnecessary copy
when sending data in a request body.
2016-01-13 03:40:41 +01:00
John Hiesey 6433ea8cad Replace buffer.buffer with more compatible version
Unforunately some browsers use a version of Buffer
that isn't based on a Uint8Array. Add logic to convert
when necessary but not make copies when unnecessary.
2016-01-13 01:33:11 +01:00
John Hiesey 9ebf801ebb Fix typos 2016-01-12 22:39:46 +01:00
John Hiesey 154f6b9e56 Fix buffer usage in tests as well 2016-01-12 22:18:41 +01:00
John Hiesey 6dea87e704 Merge pull request #36 from karissa/master
Use .buffer to comply with buffer@4 and browserify@13.
2016-01-12 21:56:37 +01:00
Karissa McKelvey 8b9f5a43c1 Use .buffer to comply with buffer@4 and browserify@13 2016-01-12 12:30:39 -08:00
John Hiesey 3c8b44379c 2.0.5 2016-01-09 03:56:05 +01:00
John Hiesey c36da3bade Use 'http:' as a default protocol
If global.location.protocol isn't 'http:' or 'https:', use
'http:'. This makes behavior reasonable when the page is
opened directly without using a server.
2016-01-09 03:55:14 +01:00
John Hiesey cbc0bd803c 2.0.4 2016-01-09 00:22:46 +01:00
John Hiesey d6de2ce3a5 Add docs on running tests 2016-01-09 00:20:56 +01:00
John Hiesey 4bcff63d46 Don't run browser tests in travis for PRs
These tests fail anyway, since the sauce labs credentials
aren't available. In all other cases running the browser
tests is desirable.
2016-01-09 00:20:26 +01:00
John Hiesey a69310d8fc Run travis tests using node 4.1
This is the latest version currently supported according
to the travis docs. The version used here shouldn't actually
affect the results of the tests, but we might as well use
something more recent.
2016-01-08 23:27:14 +01:00
John Hiesey ae48c2c71c 2.0.3 2016-01-08 22:31:45 +01:00
John Hiesey 570f85ec18 Check xhr.status is non-zero to avoid running onXHRProgress on error
Previously, onXHRProgress was being run when readyState was being
set to DONE on error, which was causing exceptions.
2015-10-13 15:46:01 -07:00
John Hiesey 85381f0e3d 2.0.2 2015-10-01 23:01:00 -07:00
John Hiesey 3bea2a493f Don't turn exceptions thrown in the callback into 'error' events
When using the fetch api, exceptions thrown inside
ClientRequest._onFinish, which also calls the user callback,
were being caught and turned into 'error' events. This is
not the correct behavior; these exceptions should remain
uncaught to let the browser print them to the console.
2015-10-01 22:26:59 -07:00
John Hiesey 016f4be96f 2.0.1 2015-09-30 13:23:16 -07:00
John Hiesey 9e5e75b5ff Merge pull request #27 from mbcrute/patch-body
Don't ignore PATCH request bodies
2015-09-30 13:10:09 -07:00
Matt Crute 031b8aab89 Stop ignoring PATCH request bodies 2015-09-30 11:01:26 -07:00
John Hiesey dc421eb355 2.0.0 2015-09-15 17:24:23 -07:00
John Hiesey 7d1dfd2ab1 Merge pull request #25 from jhiesey/polyfills
Require polyfills for IE8 support to reduce bundle size
2015-09-15 17:23:40 -07:00
John Hiesey 75817ba930 Fix tests 2015-09-15 16:54:17 -07:00
John Hiesey 2152af414a Remove implementations of ES5 functions not provided in ie8
From now on, IE8 is only supported if polyfills for `Object.keys`,
`Array.prototype.forEach`, and `Array.prototype.indexOf` are
provided. The suggested polyfills from MDN are provided in
ie8-polyfill.js, or you can use https://github.com/es-shims/es5-shim
for a more complete set of polyfills.
2015-09-15 16:54:17 -07:00
John Hiesey a10d19b3b2 Fix occasional test errors on Chrome 45
If multiple chunks of data got queued up, the abort on data test
would erroneously fail. Wait till the end of the tick before
considering 'data' events as errors.
2015-09-15 16:52:11 -07:00
John Hiesey a169930a20 1.7.1 2015-09-15 14:47:03 -07:00
John Hiesey 43d1322dd6 1.7.0 2015-09-15 00:22:13 -07:00
John Hiesey f72d22b2a9 Disable IE8 tests due to intermittent failures 2015-09-14 23:37:43 -07:00
John Hiesey bde26be949 Add WebWorker test using webworkify 2015-09-14 23:25:24 -07:00
John Hiesey 8cab16f26e Change window to global for WebWorker support 2015-09-14 18:18:53 -07:00
John Hiesey 166795fc05 1.6.0 2015-09-11 21:19:23 -07:00
John Hiesey ff339bd64c Merge pull request #21 from jhiesey/browser-resolve
Add support for relative paths in urls
2015-09-11 20:40:14 -07:00
John Hiesey 5986071393 Allow newer versions of some deps 2015-09-11 20:40:00 -07:00
John Hiesey bbf1fd81f0 Test handling of relative paths in urls
This adds the test from #17.
2015-09-11 20:09:20 -07:00
John Hiesey 6b9d3d107c Drastically simplify and improve url handling
Make options handling logic match node's `http` module more
closely and let the browser handle all of the relative url
cases itself.

This has a few important advantages:
* Behavior is closer to native `http` modula
* Relative paths in urls are handled properly (fixes #16)
* Code is simpler and more obviously correct
2015-09-11 20:06:13 -07:00
John Hiesey 3fda0048d7 Minor whitespace fixes in node test 2015-09-09 20:25:14 -07:00
John Hiesey bef2b95708 Fix IPv6 addresses in requests
Fixes #18
2015-09-09 20:25:14 -07:00
John Hiesey efed69e6e2 Merge pull request #13 from regular/patch-1
Update README.md
2015-08-28 10:46:20 -07:00
Jan Bölsche caa40a0950 Update README.md
from the code it looks like if withCredentials defaults to false now.
2015-08-28 19:40:15 +02:00
John Hiesey 168dbac90c 1.5.0 2015-08-20 01:21:15 -07:00
John Hiesey a91ed0db64 Modify feature testing to avoid warnings in Chrome
Previously, testing for 'ms-stream' and 'moz-chunked-arraybuffer'
support on the xhr object caused warnings to appear in Chrome.
Since fetch will always be used instead of these when available,
short-circuit and return false when fetch is available.
2015-08-20 01:06:34 -07:00
John Hiesey da4085e33d Make content-type header correct by default
The default behavior now preserves the 'content-type' header, at
the expense of not providing pseudo-streming in Safari, generic
WebKit, and older Chrome. Applications that require streaming
and already use 'prefer-stream' mode will not be affected.

The 'prefer-fast' mode is now deprecated, as that behavior now
matches the default. The 'allow-wrong-content-type' mode is new;
it gives the same behavior as the old default in case binary
streaming is essential.

Fixes #8
2015-08-20 01:01:40 -07:00
John Hiesey 6d17b406a9 1.4.1 2015-08-07 18:18:02 -07:00
John Hiesey 1a4a89a5e4 Add test for data in request.end() 2015-08-07 18:02:33 -07:00
John Hiesey 838aaffac7 Fix passing data to request.end()
Previously this would result in an exception.
Thanks @magnuslundstedt for finding the bug!
2015-08-07 17:55:43 -07:00
John Hiesey f70ac39bf4 1.4.0 2015-08-06 22:44:10 -07:00
John Hiesey ed525a4a2d Merge pull request #11 from tjmehta/add-http-methods-from-node
Add http methods from node v0.12
2015-08-06 22:33:45 -07:00
Tejesh Mehta b162e21d92 Add http methods from node v0.12 2015-08-06 17:39:17 -07:00
John Hiesey c8c0966c63 1.3.0 2015-07-20 16:10:29 -07:00
John Hiesey efaad2a3c7 Add keywords 2015-07-20 16:07:30 -07:00
John Hiesey 9059849673 Make withCredentials default to false
This may break compatibility with code that was written for
`http-browserify`. Fixes #7
2015-07-20 16:01:24 -07:00
John Hiesey aeabb2cc70 Merge pull request #6 from cesarandreu/consistent-fetch
Fix inconsistency between XMLHttpRequest and fetch
2015-07-20 00:32:46 -07:00
Cesar Andreu 588e5874f3 Add test for cookie behavior 2015-07-19 17:50:30 -07:00
Cesar Andreu 5fc4858d6d Use same-origin instead of omit with fetch credentials
In order to have parity with XMLHttpRequest fetch credentials should use same-origin instead of omit
Setting XMLHttpRequest.withCredentials to false has no effect on same-site request
2015-07-19 17:06:28 -07:00
John Hiesey 22b3cc7acb v1.2.1 2015-07-17 00:50:31 -07:00
John Hiesey 5ffc869490 Fix readme to point to new saucelabs account 2015-07-17 00:35:03 -07:00
John Hiesey cc74bc83c8 v1.2.0 2015-07-13 18:18:13 -07:00
John Hiesey 5e81240a05 add process.nextTick for 'close' 2015-07-13 18:14:30 -07:00
John Hiesey 29a2ad2032 Add abort() test 2015-07-13 16:57:31 -07:00
John Hiesey 6b544c54a3 Fix spelling of 'destroyed' 2015-07-13 16:45:46 -07:00
John Hiesey 734974d279 Improve behavior of request.prototype.abort() and add destroy synonym 2015-07-13 16:37:04 -07:00
John Hiesey 57ce59c207 Re-add close event on response 2015-07-13 16:07:46 -07:00
John Hiesey 15181f5586 v1.1.1 2015-07-12 21:26:03 -07:00
John Hiesey 916ab55402 Hide warnings for unsafe headers 2015-07-12 21:25:08 -07:00
John Hiesey 0f81836459 v1.1.0 2015-07-12 21:10:55 -07:00
John Hiesey 73f1e6f3f0 Stop emitting 'close'
1. This fixes an issue where the `response` module emits an extra
  'end'
2. Node doesn't seem to emit 'close', even if "Connection: close"
  is specified
3. There isn't a real socket, and this module already doesn't
  emit the other socket-related events (and this is documented)
2015-07-12 21:08:00 -07:00
John Hiesey d700e6d8d9 Fix withCredentials flag for fetch implementation 2015-07-12 20:41:20 -07:00
John Hiesey 7cb4e22c9f v1.0.2 2015-07-12 18:46:15 -07:00
John Hiesey 9cb2a8db15 Don't test on ios 5.0 since saucelabs is removing it 2015-07-12 18:45:38 -07:00
28 changed files with 983 additions and 308 deletions
+31
View File
@@ -0,0 +1,31 @@
providers:
- airtap-sauce
browsers:
- name: chrome
version: -2..latest
- name: firefox
version: -2..latest
- name: safari
version: [8..10, 12..latest]
- name: safari
version: 11
platform: Mac 10.13 # Configure separately due to https://github.com/airtap/sauce-browsers/issues/3
- name: MicrosoftEdge
version: -2..latest
- name: ie
version: 11
- name: iphone
version: '10.3..latest'
- name: android
version: '6.0..latest'
server: ./test/server/index.js
scripts:
- "/test-polyfill.js"
browserify:
- options:
dedupe: false
presets:
local:
providers: airtap-manual
browsers:
- name: manual
+2 -1
View File
@@ -1,5 +1,6 @@
.airtaprc
.DS_Store
bundle.js
node_modules
npm-debug.log
.zuulrc
package-lock.json
+3
View File
@@ -0,0 +1,3 @@
.airtap.yml
.travis.yml
test/
+5 -1
View File
@@ -1,3 +1,7 @@
language: node_js
node_js:
- "0.12"
- "node"
addons:
sauce_connect: true
hosts:
- airtap.local
-19
View File
@@ -1,19 +0,0 @@
ui: tape
browsers:
- name: chrome
version: 39..latest
- name: firefox
version: 34..latest
- name: safari
version: 5..latest
- name: ie
version: 8..latest
- name: opera
version: 11..latest
- name: iphone
version: 5.0..latest
- name: android
version: 4.0..latest
server: ./test/server/index.js
scripts:
- "/polyfill.js"
+74 -35
View File
@@ -1,77 +1,99 @@
# stream-http [![Build Status](https://travis-ci.org/jhiesey/stream-http.svg?branch=master)](https://travis-ci.org/jhiesey/stream-http)
[![Sauce Test Status](https://saucelabs.com/browser-matrix/jhiesey.svg)](https://saucelabs.com/u/jhiesey)
[![Sauce Test Status](https://app.saucelabs.com/browser-matrix/stream-http.svg)](https://app.saucelabs.com/u/stream-http)
This module is an implementation of node's native `http` module for the browser.
It tries to match node's api and behavior as closely as possible, but some features
This module is an implementation of Node's native `http` module for the browser.
It tries to match Node's API and behavior as closely as possible, but some features
aren't available, since browsers don't give nearly as much control over requests.
This is heavily inspired by, and intended to replace, [http-browserify](https://github.com/substack/http-browserify)
This is heavily inspired by, and intended to replace, [http-browserify](https://github.com/substack/http-browserify).
## What does it do?
In accordance with its name, `stream-http` tries to provide data to its caller before
the request has completed whenever possible.
Backpressure, allowing the browser to only pull data from the server as fast as it is
consumed, is supported in:
* Chrome >= 58 (using `fetch` and `WritableStream`)
The following browsers support true streaming, where only a small amount of the request
has to be held in memory at once:
* Chrome >= 43 (using the `fetch` api)
* Chrome >= 43 (using the `fetch` API)
* Firefox >= 9 (using `moz-chunked-arraybuffer` responseType with xhr)
The following browsers support pseudo-streaming, where the data is available before the
request finishes, but the entire response must be held in memory:
* Chrome
* Safari >= 5, and maybe older
* IE >= 10
* Most other Webkit-based browsers, including the default Android browser
All other supported browsers support pseudo-streaming, where the data is available before
the request finishes, but the entire response must be held in memory. This works for both
text and binary data.
All browsers newer than IE8 support binary responses. All of the above browsers that
support true streaming or pseudo-streaming support that for binary data as well
except for IE10. Old (presto-based) Opera also does not support binary streaming either.
### IE note:
As of version 3.0.0, IE10 and below are no longer supported. IE11 support will remain for
now.
## How do you use it?
The intent is to have the same api as the client part of the
[node HTTP module](https://nodejs.org/api/http.html). The interfaces are the same wherever
practical, although limitations in browsers make an exact clone of the node api impossible.
The intent is to have the same API as the client part of the
[Node HTTP module](https://nodejs.org/api/http.html). The interfaces are the same wherever
practical, although limitations in browsers make an exact clone of the Node API impossible.
This module implements `http.request`, `http.get`, and most of `http.ClientRequest`
and `http.IncomingMessage` in addition to `http.METHODS` and `http.STATUS_CODES`. See the
node docs for how these work.
Node docs for how these work.
### Extra features compared to node
### Extra features compared to Node
* The `message.url` property provides access to the final URL after all redirects. This
is useful since the browser follows all redirects silently, unlike Node. It is available
in Chrome 37 and newer, Firefox 32 and newer, and Safari 9 and newer.
* The `options.withCredentials` boolean flag, used to indicate if the browser should send
cookies or authentication information with a CORS request. Default true.
cookies or authentication information with a CORS request. Default false.
This module has to make some tradeoffs to support binary data and/or streaming. Generally,
the module can make a fairly good decision about which underlying browser features to use,
but sometimes it helps to get a little input from the user.
but sometimes it helps to get a little input from the developer.
* The `options.mode` field passed into `http.request` or `http.get` can take on one of the
following values:
* 'default' (or any falsy value, including undefined): Try to provide partial data before
the equest completes, but not at the cost of correctness for binary data. In some cases
the implementation may be a bit slow.
* 'prefer-stream': Provide data before the request completes even if binary data (anything
that isn't a single-byte ASCII or utf8 character) will be corrupted. Of course, this option
is only safe for text data.
* 'prefer-fast': Use an implementation that does less processing even if it means that
partial data isn't available. This is particularly useful when making large requests in
a browser like Safari that has a weaker javascript engine.
* 'default' (or any falsy value, including `undefined`): Try to provide partial data before
the request completes, but not at the cost of correctness for binary data or correctness of
the 'content-type' response header. This mode will also avoid slower code paths whenever
possible, which is particularly useful when making large requests in a browser like Safari
that has a weaker JavaScript engine.
* 'allow-wrong-content-type': Provides partial data in more cases than 'default', but
at the expense of causing the 'content-type' response header to be incorrectly reported
(as 'text/plain; charset=x-user-defined') in some browsers, notably Safari and Chrome 42
and older. Preserves binary data whenever possible. In some cases the implementation may
also be a bit slow. This was the default in versions of this module before 1.5.
* 'prefer-streaming': Provide data before the request completes even if binary data (anything
that isn't a single-byte ASCII or UTF8 character) will be corrupted. Of course, this option
is only safe for text data. May also cause the 'content-type' response header to be
incorrectly reported (as 'text/plain; charset=x-user-defined').
* 'disable-fetch': Force the use of plain XHR regardless of the browser declaring a fetch
capability. Preserves the correctness of binary data and the 'content-type' response header.
* 'prefer-fast': Deprecated; now a synonym for 'default', which has the same performance
characteristics as this mode did in versions before 1.5.
### Features missing compared to node
* `options.requestTimeout` allows setting a timeout in millisecionds for XHR and fetch (if
supported by the browser). This is a limit on how long the entire process takes from
beginning to end. Note that this is not the same as the node `setTimeout` functions,
which apply to pauses in data transfer over the underlying socket, or the node `timeout`
option, which applies to opening the connection.
### Features missing compared to Node
* `http.Agent` is only a stub
* The 'socket', 'connect', 'upgrade', and 'continue' events on `http.ClientRequest`.
* Any operations, including `request.setTimeout`, that operate directly on the underlying
* Any operations, other than `request.setTimeout`, that operate directly on the underlying
socket.
* Any options that are disallowed for security reasons. This includes setting or getting
certian headers.
certain headers.
* `message.httpVersion`
* `message.rawHeaders` is modified by the browser, and may not quite match what is sent by
the server.
* `message.trailers` and `message.rawTrailers` will remain empty.
* Redirects are followed silently by the browser, so it isn't possible to access the 301/302
redirect pages.
## Example
@@ -79,17 +101,34 @@ the server.
http.get('/bundle.js', function (res) {
var div = document.getElementById('result');
div.innerHTML += 'GET /beep<br>';
res.on('data', function (buf) {
div.innerHTML += buf;
});
res.on('end', function () {
div.innerHTML += '<br>__END__';
});
})
```
## Running tests
There are two sets of tests: the tests that run in Node (found in `test/node`) and the tests
that run in the browser (found in `test/browser`). Normally the browser tests run on
[Sauce Labs](http://saucelabs.com/).
Running `npm test` will run both sets of tests, but in order for the Sauce Labs tests to run
you will need to sign up for an account (free for open source projects) and put the
credentials in a [`.airtaprc` file](https://github.com/airtap/airtap/blob/master/doc/airtaprc.md).
You will also need to run a [Sauce Connect Proxy](https://wiki.saucelabs.com/display/DOCS/Sauce+Connect+Proxy)
with the same credentials.
To run just the Node tests, run `npm run test-node`.
To run the browser tests locally, run `npm run test-browser-local` and point your browser to
the link shown in your terminal.
## License
MIT. Copyright (C) John Hiesey and other contributors.
+46 -16
View File
@@ -1,4 +1,5 @@
var ClientRequest = require('./lib/request')
var response = require('./lib/response')
var extend = require('xtend')
var statusCodes = require('builtin-status-codes')
var url = require('url')
@@ -11,22 +12,24 @@ http.request = function (opts, cb) {
else
opts = extend(opts)
// Split opts.host into its components
var hostHostname = opts.host ? opts.host.split(':')[0] : null
var hostPort = opts.host ? parseInt(opts.host.split(':')[1], 10) : null
// Normally, the page is loaded from http or https, so not specifying a protocol
// will result in a (valid) protocol-relative url. However, this won't work if
// the protocol is something else, like 'file:'
var defaultProtocol = global.location.protocol.search(/^https?:$/) === -1 ? 'http:' : ''
opts.method = opts.method || 'GET'
var protocol = opts.protocol || defaultProtocol
var host = opts.hostname || opts.host
var port = opts.port
var path = opts.path || '/'
// Necessary for IPv6 addresses
if (host && host.indexOf(':') !== -1)
host = '[' + host + ']'
// This may be a relative url. The browser should always be able to interpret it correctly.
opts.url = (host ? (protocol + '//' + host) : '') + (port ? ':' + port : '') + path
opts.method = (opts.method || 'GET').toUpperCase()
opts.headers = opts.headers || {}
opts.path = opts.path || '/'
opts.protocol = opts.protocol || window.location.protocol
// If the hostname is provided, use the default port for the protocol. If
// the url is instead relative, use window.location.port
var defaultPort = (opts.hostname || hostHostname) ? (opts.protocol === 'https:' ? 443 : 80) : window.location.port
opts.hostname = opts.hostname || hostHostname || window.location.hostname
opts.port = opts.port || hostPort || defaultPort
if (opts.withCredentials === undefined)
opts.withCredentials = true
// Also valid opts.auth, opts.mode
@@ -42,14 +45,41 @@ http.get = function get (opts, cb) {
return req
}
http.ClientRequest = ClientRequest
http.IncomingMessage = response.IncomingMessage
http.Agent = function () {}
http.Agent.defaultMaxSockets = 4
http.globalAgent = new http.Agent()
http.STATUS_CODES = statusCodes
http.METHODS = [
'CHECKOUT',
'CONNECT',
'COPY',
'DELETE',
'GET',
'HEAD',
'LOCK',
'M-SEARCH',
'MERGE',
'MKACTIVITY',
'MKCOL',
'MOVE',
'NOTIFY',
'OPTIONS',
'PATCH',
'POST',
'PROPFIND',
'PROPPATCH',
'PURGE',
'PUT',
'DELETE' // TODO: include the methods from RFC 2616 and 2518?
]
'REPORT',
'SEARCH',
'SUBSCRIBE',
'TRACE',
'UNLOCK',
'UNSUBSCRIBE'
]
+42 -16
View File
@@ -1,15 +1,37 @@
exports.fetch = isFunction(window.fetch) && isFunction(window.ReadableByteStream)
exports.fetch = isFunction(global.fetch) && isFunction(global.ReadableStream)
exports.blobConstructor = false
try {
new Blob([new ArrayBuffer(1)])
exports.blobConstructor = true
} catch (e) {}
exports.writableStream = isFunction(global.WritableStream)
var xhr = new window.XMLHttpRequest()
xhr.open('GET', '/')
exports.abortController = isFunction(global.AbortController)
// The xhr request to example.com may violate some restrictive CSP configurations,
// so if we're running in a browser that supports `fetch`, avoid calling getXHR()
// and assume support for certain features below.
var xhr
function getXHR () {
// Cache the xhr value
if (xhr !== undefined) return xhr
if (global.XMLHttpRequest) {
xhr = new global.XMLHttpRequest()
// If XDomainRequest is available (ie only, where xhr might not work
// cross domain), use the page location. Otherwise use example.com
// Note: this doesn't actually make an http request.
try {
xhr.open('GET', global.XDomainRequest ? '/' : 'https://example.com')
} catch(e) {
xhr = null
}
} else {
// Service workers don't have XHR
xhr = null
}
return xhr
}
function checkTypeSupport (type) {
var xhr = getXHR()
if (!xhr) return false
try {
xhr.responseType = type
return xhr.responseType === type
@@ -17,17 +39,21 @@ function checkTypeSupport (type) {
return false
}
var haveArrayBuffer = isFunction(window.ArrayBuffer)
var haveSlice = haveArrayBuffer && isFunction(window.ArrayBuffer.prototype.slice)
// If fetch is supported, then arraybuffer will be supported too. Skip calling
// checkTypeSupport(), since that calls getXHR().
exports.arraybuffer = exports.fetch || checkTypeSupport('arraybuffer')
exports.arraybuffer = haveArrayBuffer && checkTypeSupport('arraybuffer')
exports.msstream = haveSlice && checkTypeSupport('ms-stream')
exports.mozchunkedarraybuffer = haveArrayBuffer && checkTypeSupport('moz-chunked-arraybuffer')
exports.overrideMimeType = isFunction(xhr.overrideMimeType)
exports.vbArray = isFunction(window.VBArray)
// These next two tests unavoidably show warnings in Chrome. Since fetch will always
// be used if it's available, just return false for these to avoid the warnings.
exports.msstream = !exports.fetch && checkTypeSupport('ms-stream')
exports.mozchunkedarraybuffer = !exports.fetch && checkTypeSupport('moz-chunked-arraybuffer')
// If fetch is supported, then overrideMimeType will be supported too. Skip calling
// getXHR().
exports.overrideMimeType = exports.fetch || (getXHR() ? isFunction(getXHR().overrideMimeType) : false)
function isFunction (value) {
return typeof value === 'function'
return typeof value === 'function'
}
xhr = null // Help gc
+178 -62
View File
@@ -1,16 +1,13 @@
// var Base64 = require('Base64')
var capability = require('./capability')
var foreach = require('foreach')
var keys = require('object-keys')
var response = require('./response')
var stream = require('stream')
var inherits = require('inherits')
var response = require('./response')
var stream = require('readable-stream')
var IncomingMessage = response.IncomingMessage
var rStates = response.readyStates
function decideMode (preferBinary) {
if (capability.fetch) {
function decideMode (preferBinary, useFetch) {
if (capability.fetch && useFetch) {
return 'fetch'
} else if (capability.mozchunkedarraybuffer) {
return 'moz-chunked-arraybuffer'
@@ -18,8 +15,6 @@ function decideMode (preferBinary) {
return 'ms-stream'
} else if (capability.arraybuffer && preferBinary) {
return 'arraybuffer'
} else if (capability.vbArray && preferBinary) {
return 'text:vbarray'
} else {
return 'text'
}
@@ -30,29 +25,37 @@ var ClientRequest = module.exports = function (opts) {
stream.Writable.call(self)
self._opts = opts
self._url = opts.protocol + '//' + opts.hostname + ':' + opts.port + opts.path
self._body = []
self._headers = {}
if (opts.auth)
self.setHeader('Authorization', 'Basic ' + new Buffer(opts.auth).toString('base64'))
foreach(keys(opts.headers), function (name) {
self.setHeader('Authorization', 'Basic ' + Buffer.from(opts.auth).toString('base64'))
Object.keys(opts.headers).forEach(function (name) {
self.setHeader(name, opts.headers[name])
})
var preferBinary
if (opts.mode === 'prefer-streaming') {
// If streaming is a high priority but binary compatibility isn't
preferBinary = false
} else if (opts.mode === 'prefer-fast') {
// If binary is preferred for speed
var useFetch = true
if (opts.mode === 'disable-fetch' || ('requestTimeout' in opts && !capability.abortController)) {
// If the use of XHR should be preferred. Not typically needed.
useFetch = false
preferBinary = true
} else if (!opts.mode || opts.mode === 'default') {
// By default, use binary if text streaming may corrupt data
} else if (opts.mode === 'prefer-streaming') {
// If streaming is a high priority but binary compatibility and
// the accuracy of the 'content-type' header aren't
preferBinary = false
} else if (opts.mode === 'allow-wrong-content-type') {
// If streaming is more important than preserving the 'content-type' header
preferBinary = !capability.overrideMimeType
} else if (!opts.mode || opts.mode === 'default' || opts.mode === 'prefer-fast') {
// Use binary if text streaming may corrupt data or the content-type header, or for speed
preferBinary = true
} else {
throw new Error('Invalid value for opts.mode')
}
self._mode = decideMode(preferBinary)
self._mode = decideMode(preferBinary, useFetch)
self._fetchTimer = null
self._socketTimeout = null
self._socketTimer = null
self.on('finish', function () {
self._onFinish()
@@ -63,15 +66,24 @@ inherits(ClientRequest, stream.Writable)
ClientRequest.prototype.setHeader = function (name, value) {
var self = this
self._headers[name.toLowerCase()] = {
var lowerName = name.toLowerCase()
// This check is not necessary, but it prevents warnings from browsers about setting unsafe
// headers. To be honest I'm not entirely sure hiding these warnings is a good thing, but
// http-browserify did it, so I will too.
if (unsafeHeaders.indexOf(lowerName) !== -1)
return
self._headers[lowerName] = {
name: name,
value: value
}
}
ClientRequest.prototype.getHeader = function (name) {
var self = this
return self._headers[name.toLowerCase()].value
var header = this._headers[name.toLowerCase()]
if (header)
return header.value
return null
}
ClientRequest.prototype.removeHeader = function (name) {
@@ -82,44 +94,72 @@ ClientRequest.prototype.removeHeader = function (name) {
ClientRequest.prototype._onFinish = function () {
var self = this
if (self._destroyed)
return
var opts = self._opts
var headersObj = self._headers
var body
if (opts.method === 'POST' || opts.method === 'PUT') {
if (capability.blobConstructor) {
body = new window.Blob(self._body.map(function (buffer) {
return buffer.toArrayBuffer()
}), {
type: (headersObj['content-type'] || {}).value || ''
})
} else {
// get utf8 string
body = Buffer.concat(self._body).toString()
}
if ('timeout' in opts && opts.timeout !== 0) {
self.setTimeout(opts.timeout)
}
if (self._mode === 'fetch') {
var headers = keys(headersObj).map(function (name) {
return [headersObj[name].name, headersObj[name].value]
})
var headersObj = self._headers
var body = null
if (opts.method !== 'GET' && opts.method !== 'HEAD') {
body = new Blob(self._body, {
type: (headersObj['content-type'] || {}).value || ''
});
}
window.fetch(self._url, {
// create flattened list of headers
var headersList = []
Object.keys(headersObj).forEach(function (keyName) {
var name = headersObj[keyName].name
var value = headersObj[keyName].value
if (Array.isArray(value)) {
value.forEach(function (v) {
headersList.push([name, v])
})
} else {
headersList.push([name, value])
}
})
if (self._mode === 'fetch') {
var signal = null
if (capability.abortController) {
var controller = new AbortController()
signal = controller.signal
self._fetchAbortController = controller
if ('requestTimeout' in opts && opts.requestTimeout !== 0) {
self._fetchTimer = global.setTimeout(function () {
self.emit('requestTimeout')
if (self._fetchAbortController)
self._fetchAbortController.abort()
}, opts.requestTimeout)
}
}
global.fetch(self._opts.url, {
method: self._opts.method,
headers: headers,
body: body,
headers: headersList,
body: body || undefined,
mode: 'cors',
credentials: opts.credentials ? 'include' : 'omit'
credentials: opts.withCredentials ? 'include' : 'same-origin',
signal: signal
}).then(function (response) {
self._fetchResponse = response
self._resetTimers(false)
self._connect()
}).then(undefined, function (reason) {
self.emit('error', reason)
}, function (reason) {
self._resetTimers(true)
if (!self._destroyed)
self.emit('error', reason)
})
} else {
var xhr = self._xhr = new window.XMLHttpRequest()
var xhr = self._xhr = new global.XMLHttpRequest()
try {
xhr.open(self._opts.method, self._url, true)
xhr.open(self._opts.method, self._opts.url, true)
} catch (err) {
process.nextTick(function () {
self.emit('error', err)
@@ -129,7 +169,7 @@ ClientRequest.prototype._onFinish = function () {
// Can't set responseType on really old browsers
if ('responseType' in xhr)
xhr.responseType = self._mode.split(':')[0]
xhr.responseType = self._mode
if ('withCredentials' in xhr)
xhr.withCredentials = !!opts.withCredentials
@@ -137,8 +177,15 @@ ClientRequest.prototype._onFinish = function () {
if (self._mode === 'text' && 'overrideMimeType' in xhr)
xhr.overrideMimeType('text/plain; charset=x-user-defined')
foreach(keys(headersObj), function (name) {
xhr.setRequestHeader(headersObj[name].name, headersObj[name].value)
if ('requestTimeout' in opts) {
xhr.timeout = opts.requestTimeout
xhr.ontimeout = function () {
self.emit('requestTimeout')
}
}
headersList.forEach(function (header) {
xhr.setRequestHeader(header[0], header[1])
})
self._response = null
@@ -159,6 +206,9 @@ ClientRequest.prototype._onFinish = function () {
}
xhr.onerror = function () {
if (self._destroyed)
return
self._resetTimers(true)
self.emit('error', new Error('XHR error'))
}
@@ -174,12 +224,14 @@ ClientRequest.prototype._onFinish = function () {
}
/**
* Checks if xhr.status is readable. Even though the spec says it should
* be available in readyState 3, accessing it throws an exception in IE8
* Checks if xhr.status is readable and non-zero, indicating no error.
* Even though the spec says it should be available in readyState 3,
* accessing it throws an exception in IE8
*/
function statusValid (xhr) {
try {
return (xhr.status !== null)
var status = xhr.status
return (status !== null && status !== 0)
} catch (e) {
return false
}
@@ -188,19 +240,28 @@ function statusValid (xhr) {
ClientRequest.prototype._onXHRProgress = function () {
var self = this
if (!statusValid(self._xhr) || self._failed)
self._resetTimers(false)
if (!statusValid(self._xhr) || self._destroyed)
return
if (!self._response)
self._connect()
self._response._onXHRProgress()
self._response._onXHRProgress(self._resetTimers.bind(self))
}
ClientRequest.prototype._connect = function () {
var self = this
self._response = new IncomingMessage(self._xhr, self._fetchResponse, self._mode)
if (self._destroyed)
return
self._response = new IncomingMessage(self._xhr, self._fetchResponse, self._mode, self._resetTimers.bind(self))
self._response.on('error', function(err) {
self.emit('error', err)
})
self.emit('response', self._response)
}
@@ -211,10 +272,35 @@ ClientRequest.prototype._write = function (chunk, encoding, cb) {
cb()
}
ClientRequest.prototype.abort = function () {
ClientRequest.prototype._resetTimers = function (done) {
var self = this
global.clearTimeout(self._socketTimer)
self._socketTimer = null
if (done) {
global.clearTimeout(self._fetchTimer)
self._fetchTimer = null
} else if (self._socketTimeout) {
self._socketTimer = global.setTimeout(function () {
self.emit('timeout')
}, self._socketTimeout)
}
}
ClientRequest.prototype.abort = ClientRequest.prototype.destroy = function (err) {
var self = this
self._destroyed = true
self._resetTimers(true)
if (self._response)
self._response._destroyed = true
if (self._xhr)
self._xhr.abort()
else if (self._fetchAbortController)
self._fetchAbortController.abort()
if (err)
self.emit('error', err)
}
ClientRequest.prototype.end = function (data, encoding, cb) {
@@ -224,13 +310,43 @@ ClientRequest.prototype.end = function (data, encoding, cb) {
data = undefined
}
if (data)
stream.Writable.push.call(self, data, encoding)
stream.Writable.prototype.end.call(self, data, encoding, cb)
}
stream.Writable.prototype.end.call(self, cb)
ClientRequest.prototype.setTimeout = function (timeout, cb) {
var self = this
if (cb)
self.once('timeout', cb)
self._socketTimeout = timeout
self._resetTimers(false)
}
ClientRequest.prototype.flushHeaders = function () {}
ClientRequest.prototype.setTimeout = function () {}
ClientRequest.prototype.setNoDelay = function () {}
ClientRequest.prototype.setSocketKeepAlive = function () {}
// Taken from http://www.w3.org/TR/XMLHttpRequest/#the-setrequestheader%28%29-method
var unsafeHeaders = [
'accept-charset',
'accept-encoding',
'access-control-request-headers',
'access-control-request-method',
'connection',
'content-length',
'cookie',
'cookie2',
'date',
'dnt',
'expect',
'host',
'keep-alive',
'origin',
'referer',
'te',
'trailer',
'transfer-encoding',
'upgrade',
'via'
]
+89 -44
View File
@@ -1,7 +1,6 @@
var capability = require('./capability')
var foreach = require('foreach')
var stream = require('stream')
var inherits = require('inherits')
var stream = require('readable-stream')
var rStates = exports.readyStates = {
UNSENT: 0,
@@ -11,7 +10,7 @@ var rStates = exports.readyStates = {
DONE: 4
}
var IncomingMessage = exports.IncomingMessage = function (xhr, response, mode) {
var IncomingMessage = exports.IncomingMessage = function (xhr, response, mode, resetTimers) {
var self = this
stream.Readable.call(self)
@@ -21,48 +20,103 @@ var IncomingMessage = exports.IncomingMessage = function (xhr, response, mode) {
self.trailers = {}
self.rawTrailers = []
// Fake the 'close' event, but only once 'end' fires
self.on('end', function () {
// The nextTick is necessary to prevent the 'request' module from causing an infinite loop
process.nextTick(function () {
self.emit('close')
})
})
if (mode === 'fetch') {
self._fetchResponse = response
self.url = response.url
self.statusCode = response.status
self.statusMessage = response.statusText
// backwards compatible version of for (<item> of <iterable>):
// for (var <item>,_i,_it = <iterable>[Symbol.iterator](); <item> = (_i = _it.next()).value,!_i.done;)
for (var header, _i, _it = response.headers[Symbol.iterator](); header = (_i = _it.next()).value, !_i.done;) {
self.headers[header[0].toLowerCase()] = header[1]
self.rawHeaders.push(header[0], header[1])
}
response.headers.forEach(function (header, key){
self.headers[key.toLowerCase()] = header
self.rawHeaders.push(key, header)
})
// TODO: this doesn't respect backpressure. Once WritableStream is available, this can be fixed
if (capability.writableStream) {
var writable = new WritableStream({
write: function (chunk) {
resetTimers(false)
return new Promise(function (resolve, reject) {
if (self._destroyed) {
reject()
} else if(self.push(Buffer.from(chunk))) {
resolve()
} else {
self._resumeFetch = resolve
}
})
},
close: function () {
resetTimers(true)
if (!self._destroyed)
self.push(null)
},
abort: function (err) {
resetTimers(true)
if (!self._destroyed)
self.emit('error', err)
}
})
try {
response.body.pipeTo(writable).catch(function (err) {
resetTimers(true)
if (!self._destroyed)
self.emit('error', err)
})
return
} catch (e) {} // pipeTo method isn't defined. Can't find a better way to feature test this
}
// fallback for when writableStream or pipeTo aren't available
var reader = response.body.getReader()
function read () {
reader.read().then(function (result) {
if (self._destroyed)
return
resetTimers(result.done)
if (result.done) {
self.push(null)
self.emit('close')
return
}
self.push(new Buffer(result.value))
self.push(Buffer.from(result.value))
read()
}).catch(function (err) {
resetTimers(true)
if (!self._destroyed)
self.emit('error', err)
})
}
read()
} else {
self._xhr = xhr
self._pos = 0
self.url = xhr.responseURL
self.statusCode = xhr.status
self.statusMessage = xhr.statusText
var headers = xhr.getAllResponseHeaders().split(/\r?\n/)
foreach(headers, function (header) {
headers.forEach(function (header) {
var matches = header.match(/^([^:]+):\s*(.*)/)
if (matches) {
var key = matches[1].toLowerCase()
if (self.headers[key] !== undefined)
if (key === 'set-cookie') {
if (self.headers[key] === undefined) {
self.headers[key] = []
}
self.headers[key].push(matches[2])
} else if (self.headers[key] !== undefined) {
self.headers[key] += ', ' + matches[2]
else
} else {
self.headers[key] = matches[2]
}
self.rawHeaders.push(matches[1], matches[2])
}
})
@@ -84,38 +138,29 @@ var IncomingMessage = exports.IncomingMessage = function (xhr, response, mode) {
inherits(IncomingMessage, stream.Readable)
IncomingMessage.prototype._read = function () {}
IncomingMessage.prototype._read = function () {
var self = this
IncomingMessage.prototype._onXHRProgress = function () {
var resolve = self._resumeFetch
if (resolve) {
self._resumeFetch = null
resolve()
}
}
IncomingMessage.prototype._onXHRProgress = function (resetTimers) {
var self = this
var xhr = self._xhr
var response = null
switch (self._mode) {
case 'text:vbarray': // For IE9
if (xhr.readyState !== rStates.DONE)
break
try {
// This fails in IE8
response = new window.VBArray(xhr.responseBody).toArray()
} catch (e) {}
if (response !== null) {
self.push(new Buffer(response))
break
}
// Falls through in IE8
case 'text':
try { // This will fail when readyState = 3 in IE9. Switch mode and wait for readyState = 4
response = xhr.responseText
} catch (e) {
self._mode = 'text:vbarray'
break
}
response = xhr.responseText
if (response.length > self._pos) {
var newData = response.substr(self._pos)
if (self._charset === 'x-user-defined') {
var buffer = new Buffer(newData.length)
var buffer = Buffer.alloc(newData.length)
for (var i = 0; i < newData.length; i++)
buffer[i] = newData.charCodeAt(i) & 0xff
@@ -127,31 +172,31 @@ IncomingMessage.prototype._onXHRProgress = function () {
}
break
case 'arraybuffer':
if (xhr.readyState !== rStates.DONE)
if (xhr.readyState !== rStates.DONE || !xhr.response)
break
response = xhr.response
self.push(new Buffer(new Uint8Array(response)))
self.push(Buffer.from(new Uint8Array(response)))
break
case 'moz-chunked-arraybuffer': // take whole
response = xhr.response
if (xhr.readyState !== rStates.LOADING || !response)
break
self.push(new Buffer(new Uint8Array(response)))
self.push(Buffer.from(new Uint8Array(response)))
break
case 'ms-stream':
response = xhr.response
if (xhr.readyState !== rStates.LOADING)
break
var reader = new window.MSStreamReader()
var reader = new global.MSStreamReader()
reader.onprogress = function () {
if (reader.result.byteLength > self._pos) {
self.push(new Buffer(new Uint8Array(reader.result.slice(self._pos))))
self.push(Buffer.from(new Uint8Array(reader.result.slice(self._pos))))
self._pos = reader.result.byteLength
}
}
reader.onload = function () {
resetTimers(true)
self.push(null)
self.emit('close')
}
// reader.onerror = ??? // TODO: this
reader.readAsArrayBuffer(response)
@@ -160,7 +205,7 @@ IncomingMessage.prototype._onXHRProgress = function () {
// The ms-stream case handles end separately in reader.onload()
if (self._xhr.readyState === rStates.DONE && self._mode !== 'ms-stream') {
resetTimers(true)
self.push(null)
self.emit('close')
}
}
+29 -15
View File
@@ -1,6 +1,6 @@
{
"name": "stream-http",
"version": "1.0.1",
"version": "3.2.0",
"description": "Streaming http in the browser",
"main": "index.js",
"repository": {
@@ -8,26 +8,40 @@
"url": "git://github.com/jhiesey/stream-http.git"
},
"scripts": {
"test": "npm run test-node && npm run test-browser",
"test": "npm run test-node && ([ -n \"${TRAVIS_PULL_REQUEST}\" -a \"${TRAVIS_PULL_REQUEST}\" != 'false' ] || npm run test-browser)",
"test-node": "tape test/node/*.js",
"test-browser": "zuul -- test/browser/*.js",
"test-browser-local": "zuul --local 8080 --no-coverage -- test/browser/*.js"
"test-browser": "airtap --concurrency 1 -- test/browser/*.js",
"test-browser-local": "airtap --preset local -- test/browser/*.js"
},
"author": "John Hiesey",
"license": "MIT",
"bugs": {
"url": "https://github.com/jhiesey/stream-http/issues"
},
"homepage": "https://github.com/jhiesey/stream-http#readme",
"keywords": [
"http",
"stream",
"streaming",
"xhr",
"http-browserify"
],
"dependencies": {
"builtin-status-codes": "~1.0.0",
"foreach": "^2.0.5",
"inherits": "^2.0.1",
"object-keys": "1.0.4",
"xtend": "^4.0.0"
"builtin-status-codes": "^3.0.0",
"inherits": "^2.0.4",
"readable-stream": "^3.6.0",
"xtend": "^4.0.2"
},
"devDependencies": {
"basic-auth": "^1.0.3",
"brfs": "^1.4.0",
"express": "^4.13.0",
"tape": "^4.0.0",
"ua-parser-js": "^0.7.7",
"zuul": "^3.1.0"
"airtap": "^4.0.3",
"airtap-manual": "^1.0.0",
"airtap-sauce": "^1.1.0",
"basic-auth": "^2.0.1",
"brfs": "^2.0.2",
"cookie-parser": "^1.4.5",
"express": "^4.17.1",
"tape": "^5.2.2",
"ua-parser-js": "^0.7.28",
"webworkify": "^1.5.0"
}
}
+55
View File
@@ -0,0 +1,55 @@
var Buffer = require('buffer').Buffer
var fs = require('fs')
var test = require('tape')
var http = require('../..')
test('abort before response', function (t) {
var req = http.get('/basic.txt', function (res) {
t.fail('unexpected response')
})
req.abort()
t.end()
})
test('abort on response', function (t) {
var req = http.get('/basic.txt', function (res) {
req.abort()
t.end()
res.on('end', function () {
t.fail('unexpected end')
})
res.on('data', function (data) {
t.fail('unexpected data')
})
})
})
test('abort on data', function (t) {
var req = http.get('/browserify.png?copies=5', function (res) {
var firstData = true
var failOnData = false
res.on('end', function () {
t.fail('unexpected end')
})
res.on('data', function (data) {
if (failOnData)
t.fail('unexpected data')
else if (firstData) {
firstData = false
req.abort()
t.end()
process.nextTick(function () {
// Wait for any data that may have been queued
// in the stream before considering data events
// as errors
failOnData = true
})
}
})
})
})
+9 -27
View File
@@ -5,18 +5,7 @@ var UAParser = require('ua-parser-js')
var http = require('../..')
var browser = (new UAParser()).setUA(navigator.userAgent).getBrowser()
var browserName = browser.name
var browserVersion = browser.major
// Binary streaming doesn't work in IE10 or below or in Opera
var skipStreamingCheck = (browserName === 'Opera' || (browserName === 'IE' && browserVersion <= 10))
// Binary data gets corrupted in IE8 or below
var skipVerification = (browserName === 'IE' && browserVersion <= 8)
// IE8 tends to throw up modal dialogs complaining about scripts running too long
// Since streaming doesn't actually work there anyway, just use one copy
var COPIES = skipVerification ? 1 : 20
var COPIES = 20
var MIN_PIECES = 2
var referenceOnce = fs.readFileSync(__dirname + '/../server/static/browserify.png')
@@ -26,18 +15,14 @@ for(var i = 0; i < COPIES; i++) {
}
test('binary streaming', function (t) {
http.get('/browserify.png?copies=' + COPIES, function (res) {
http.get({
path: '/browserify.png?copies=' + COPIES,
mode: 'allow-wrong-content-type'
}, function (res) {
var buffers = []
res.on('end', function () {
if (skipVerification)
t.skip('binary data not preserved on IE <= 8')
else
t.ok(reference.equals(Buffer.concat(buffers)), 'contents match')
if (skipStreamingCheck)
t.skip('streaming not available on IE <= 8')
else
t.ok(buffers.length >= MIN_PIECES, 'received in multiple parts')
t.ok(reference.equals(Buffer.concat(buffers)), 'contents match')
t.ok(buffers.length >= MIN_PIECES, 'received in multiple parts')
t.end()
})
@@ -50,14 +35,11 @@ test('binary streaming', function (t) {
test('large binary request without streaming', function (t) {
http.get({
path: '/browserify.png?copies=' + COPIES,
mode: 'prefer-fast',
mode: 'default',
}, function (res) {
var buffers = []
res.on('end', function () {
if (skipVerification)
t.skip('binary data not preserved on IE <= 8')
else
t.ok(reference.equals(Buffer.concat(buffers)), 'contents match')
t.ok(reference.equals(Buffer.concat(buffers)), 'contents match')
t.end()
})
+1 -10
View File
@@ -5,12 +5,6 @@ var UAParser = require('ua-parser-js')
var http = require('../..')
var browser = (new UAParser()).setUA(navigator.userAgent).getBrowser()
var browserName = browser.name
var browserVersion = browser.major
// Binary data gets corrupted in IE8 or below
var skipVerification = (browserName === 'IE' && browserVersion <= 8)
var reference = fs.readFileSync(__dirname + '/../server/static/browserify.png')
test('binary download', function (t) {
@@ -18,10 +12,7 @@ test('binary download', function (t) {
var buffers = []
res.on('end', function () {
if (skipVerification)
t.skip('binary data not preserved on IE <= 8')
else
t.ok(reference.equals(Buffer.concat(buffers)), 'contents match')
t.ok(reference.equals(Buffer.concat(buffers)), 'contents match')
t.end()
})
+29
View File
@@ -0,0 +1,29 @@
var Buffer = require('buffer').Buffer
var fs = require('fs')
var test = require('tape')
var http = require('../..')
var reference = fs.readFileSync(__dirname + '/../server/static/basic.txt')
test('get body empty', function (t) {
var req = http.request({
path: '/verifyEmpty',
method: 'GET'
}, function (res) {
var buffers = []
res.on('end', function () {
console.log(Buffer.concat(buffers).toString('utf8'))
t.ok(Buffer.from('empty').equals(Buffer.concat(buffers)), 'response body indicates request body was empty')
t.end()
})
res.on('data', function (data) {
buffers.push(data)
})
})
req.write(reference)
req.end()
})
+25
View File
@@ -0,0 +1,25 @@
var Buffer = require('buffer').Buffer
var test = require('tape')
var http = require('../..')
test('cookie', function (t) {
var cookie = 'hello=world'
window.document.cookie = cookie
http.get({
path: '/cookie',
withCredentials: false
}, function (res) {
var buffers = []
res.on('end', function () {
t.ok(new Buffer(cookie).equals(Buffer.concat(buffers)), 'hello cookie echoed')
t.end()
})
res.on('data', function (data) {
buffers.push(data)
})
})
})
+37
View File
@@ -0,0 +1,37 @@
var Buffer = require('buffer').Buffer
var test = require('tape')
var http = require('../..')
test('disable fetch', function (t) {
var originalFetch
if (typeof fetch === 'function') {
originalFetch = fetch
}
var fetchCalled = false
fetch = function (input, options) {
fetchCalled = true
if (originalFetch) {
return originalFetch(input, options)
}
}
http.get({
path: '/browserify.png',
mode: 'disable-fetch'
}, function (res) {
t.ok(!fetchCalled, 'fetch was not called')
if (originalFetch) {
fetch = originalFetch
}
res.on('end', function () {
t.ok(res.headers['content-type'] === 'image/png', 'content-type was set correctly')
t.end()
})
res.on('data', function () {})
})
})
+73 -13
View File
@@ -1,19 +1,10 @@
var Buffer = require('buffer').Buffer
var fs = require('fs')
var keys = require('object-keys')
var test = require('tape')
var UAParser = require('ua-parser-js')
var http = require('../..')
function indexOf (arr, item) {
var len = arr.length
for (var i = 0; i < len; i++) {
if (arr[i] === item)
return i
}
return -1
}
test('headers', function (t) {
http.get({
path: '/testHeaders?Response-Header=bar&Response-Header-2=BAR2',
@@ -28,10 +19,10 @@ test('headers', function (t) {
if (lowerKey.indexOf('test-') === 0)
rawHeaders.push(lowerKey, res.rawHeaders[i + 1])
}
var header1Pos = indexOf(rawHeaders, 'test-response-header')
var header1Pos = rawHeaders.indexOf('test-response-header')
t.ok(header1Pos >= 0, 'raw response header 1 present')
t.equal(rawHeaders[header1Pos + 1], 'bar', 'raw response header value 1')
var header2Pos = indexOf(rawHeaders, 'test-response-header-2')
var header2Pos = rawHeaders.indexOf('test-response-header-2')
t.ok(header2Pos >= 0, 'raw response header 2 present')
t.equal(rawHeaders[header2Pos + 1], 'BAR2', 'raw response header value 2')
t.equal(rawHeaders.length, 4, 'correct number of raw headers')
@@ -45,7 +36,7 @@ test('headers', function (t) {
var body = JSON.parse(Buffer.concat(buffers).toString())
t.equal(body['test-request-header'], 'foo', 'request header 1')
t.equal(body['test-request-header-2'], 'FOO2', 'request header 2')
t.equal(keys(body).length, 2, 'correct number of request headers')
t.equal(Object.keys(body).length, 2, 'correct number of request headers')
t.end()
})
@@ -53,4 +44,73 @@ test('headers', function (t) {
buffers.push(data)
})
})
})
test('arrays of headers', function (t) {
http.get({
path: '/testHeaders?Response-Header=bar&Response-Header=BAR2',
headers: {
'Test-Request-Header': ['foo', 'FOO2']
}
}, function (res) {
var rawHeaders = []
for (var i = 0; i < res.rawHeaders.length; i += 2) {
var lowerKey = res.rawHeaders[i].toLowerCase()
if (lowerKey.indexOf('test-') === 0)
rawHeaders.push(lowerKey, res.rawHeaders[i + 1])
}
t.equal(rawHeaders[0], 'test-response-header', 'raw response header present')
t.equal(rawHeaders[1], 'bar, BAR2', 'raw response header value')
t.equal(rawHeaders.length, 2, 'correct number of raw headers')
t.equal(res.headers['test-response-header'], 'bar, BAR2', 'response header')
var buffers = []
res.on('end', function () {
var body = JSON.parse(Buffer.concat(buffers).toString())
t.equal(body['test-request-header'], 'foo,FOO2', 'request headers')
t.equal(Object.keys(body).length, 1, 'correct number of request headers')
t.end()
})
res.on('data', function (data) {
buffers.push(data)
})
})
})
test('content-type response header', function (t) {
http.get('/testHeaders', function (res) {
t.equal(res.headers['content-type'], 'application/json', 'content-type preserved')
t.end()
})
})
var browser = (new UAParser()).setUA(navigator.userAgent).getBrowser()
var browserName = browser.name
var browserVersion = browser.major
var browserMinorVersion = browser.minor || 0
// The content-type header is broken when 'prefer-streaming' or 'allow-wrong-content-type'
// is passed in browsers that rely on xhr.overrideMimeType(), namely older chrome, safari 6-10.0, and the stock Android browser
// Note that Safari 10.0 on iOS 10.3 doesn't need to override the mime type, so the content-type is preserved.
var wrongMimeType = ((browserName === 'Chrome' && browserVersion <= 42) ||
((browserName === 'Safari' || browserName === 'Mobile Safari') && browserVersion >= 6 && (browserVersion < 10 || (browserVersion == 10 && browserMinorVersion == 0)))
|| (browserName === 'Android Browser'))
test('content-type response header with forced streaming', function (t) {
http.get({
path: '/testHeaders',
mode: 'prefer-streaming'
}, function (res) {
if (wrongMimeType) {
// allow both the 'wrong' and correct mime type, since sometimes it's impossible to tell which to expect
// from the browser version alone (e.g. Safari 10.0 on iOS 10.2 vs iOS 10.3)
var contentType = res.headers['content-type']
var correct = (contentType === 'text/plain; charset=x-user-defined') || (contentType === 'application/json')
t.ok(correct, 'content-type either preserved or overridden')
} else
t.equal(res.headers['content-type'], 'application/json', 'content-type preserved')
t.end()
})
})
+20
View File
@@ -0,0 +1,20 @@
var Buffer = require('buffer').Buffer
var http = require('../../..')
module.exports = function (self) {
self.addEventListener('message', function (ev) {
var url = ev.data
http.get(url, function (res) {
var buffers = []
res.on('end', function () {
self.postMessage(Buffer.concat(buffers).buffer)
})
res.on('data', function (data) {
buffers.push(data)
})
})
})
}
+1 -14
View File
@@ -5,16 +5,6 @@ var UAParser = require('ua-parser-js')
var http = require('../..')
var browser = (new UAParser()).setUA(navigator.userAgent).getBrowser()
var browserName = browser.name
var browserVersion = browser.major
// Binary request bodies don't work in a bunch of browsers
var skipVerification = ((browserName === 'Opera' && browserVersion <= 11) ||
(browserName === 'IE' && browserVersion <= 10) ||
(browserName === 'Safari' && browserVersion <= 5) ||
(browserName === 'WebKit' && browserVersion <= 534) || // Old mobile safari
(browserName === 'Android Browser' && browserVersion <= 4))
var reference = fs.readFileSync(__dirname + '/../server/static/browserify.png')
test('post binary', function (t) {
@@ -25,10 +15,7 @@ test('post binary', function (t) {
var buffers = []
res.on('end', function () {
if (skipVerification)
t.skip('binary data not preserved on this browser')
else
t.ok(reference.equals(Buffer.concat(buffers)), 'echoed contents match')
t.ok(reference.equals(Buffer.concat(buffers)), 'echoed contents match')
t.end()
})
+20
View File
@@ -25,4 +25,24 @@ test('post text', function (t) {
req.write(reference)
req.end()
})
test('post text with data in end()', function (t) {
var req = http.request({
path: '/echo',
method: 'POST'
}, function (res) {
var buffers = []
res.on('end', function () {
t.ok(reference.equals(Buffer.concat(buffers)), 'echoed contents match')
t.end()
})
res.on('data', function (data) {
buffers.push(data)
})
})
req.end(reference)
})
+3 -6
View File
@@ -8,8 +8,8 @@ var http = require('../..')
var browser = (new UAParser()).setUA(navigator.userAgent).getBrowser()
var browserName = browser.name
var browserVersion = browser.major
// Streaming doesn't work in IE9 or below or in Opera
var skipStreamingCheck = (browserName === 'Opera' || (browserName === 'IE' && browserVersion <= 9))
// Streaming doesn't work in IE9 or below
var skipStreamingCheck = (browserName === 'IE' && browserVersion <= 9)
var COPIES = 1000
var MIN_PIECES = 5
@@ -28,10 +28,7 @@ test('text streaming', function (t) {
var buffers = []
res.on('end', function () {
if (skipStreamingCheck)
t.skip('streaming not available on IE <= 8')
else
t.ok(buffers.length >= MIN_PIECES, 'received in multiple parts')
t.ok(buffers.length >= MIN_PIECES, 'received in multiple parts')
t.ok(reference.equals(Buffer.concat(buffers)), 'contents match')
t.end()
})
+20
View File
@@ -1,13 +1,33 @@
var Buffer = require('buffer').Buffer
var fs = require('fs')
var test = require('tape')
var UAParser = require('ua-parser-js')
var url = require('url')
var http = require('../..')
var browser = (new UAParser()).setUA(navigator.userAgent).getBrowser()
var browserName = browser.name
var browserVersion = browser.major
// Response urls don't work on many browsers
var skipResponseUrl = ((browserName === 'IE') ||
(browserName === 'Edge') ||
(browserName === 'Chrome' && browserVersion <= 36) ||
(browserName === 'Firefox' && browserVersion <= 31) ||
((browserName === 'Safari' || browserName === 'Mobile Safari') && browserVersion <= 8) ||
(browserName === 'Android Browser' && browserVersion <= 4))
var reference = fs.readFileSync(__dirname + '/../server/static/basic.txt')
test('basic functionality', function (t) {
http.get('/basic.txt', function (res) {
if (!skipResponseUrl) {
var testUrl = url.resolve(global.location.href, '/basic.txt')
// Redirects aren't tested, but presumably only browser bugs
// would cause this to fail only after redirects.
t.equals(res.url, testUrl, 'response url correct')
}
var buffers = []
res.on('end', function () {
+63
View File
@@ -0,0 +1,63 @@
var Buffer = require('buffer').Buffer
var fs = require('fs')
var test = require('tape')
var http = require('../..')
test('requestTimeout', function (t) {
var req = http.get({
path: '/browserify.png?copies=5',
requestTimeout: 10 // ms
}, function (res) {
res.on('data', function (data) {
})
res.on('end', function () {
t.fail('request completed (should have timed out)')
})
})
req.on('requestTimeout', function () {
t.pass('got requestTimeout')
t.end()
})
})
// TODO: reenable this if there's a way to make it simultaneously
// fast and reliable
test.skip('no requestTimeout after success', function (t) {
var req = http.get({
path: '/basic.txt',
requestTimeout: 50000 // ms
}, function (res) {
res.on('data', function (data) {
})
res.on('end', function () {
t.pass('success')
global.setTimeout(function () {
t.end()
}, 50000)
})
})
req.on('requestTimeout', function () {
t.fail('unexpected requestTimeout')
})
})
test('setTimeout', function (t) {
t.plan(2)
var req = http.get({
path: '/browserify.png?copies=5'
}, function (res) {
res.on('data', function (data) {
})
res.on('end', function () {
t.fail('request completed (should have timed out)')
})
})
req.setTimeout(10, function () {
t.pass('got setTimeout callback')
})
req.on('timeout', function () {
t.pass('got timeout')
})
})
+21
View File
@@ -0,0 +1,21 @@
var fs = require('fs')
var test = require('tape')
var UAParser = require('ua-parser-js')
var url = require('url')
var work = require('webworkify')
var reference = fs.readFileSync(__dirname + '/../server/static/browserify.png')
// Temporarily disabled due to https://github.com/browserify/webworkify/issues/41
test.skip('binary download in WebWorker', function (t) {
// We have to use a global url, since webworkify puts the worker in a Blob,
// which doesn't have a proper location
var testUrl = url.resolve(global.location.href, '/browserify.png')
var worker = work(require('./lib/webworker-worker.js'))
worker.addEventListener('message', function (ev) {
var data = new Buffer(new Uint8Array(ev.data))
t.ok(reference.equals(data), 'contents match')
t.end()
})
worker.postMessage(testUrl)
})
+75 -25
View File
@@ -1,16 +1,13 @@
// These tests are teken from http-browserify to ensure compatibility with
// These tests are taken from http-browserify to ensure compatibility with
// that module
var test = require('tape')
var url = require('url')
global.window = {}
window.location = {
hostname: 'localhost',
port: 8081,
protocol: 'http:'
}
var location = 'http://localhost:8081/foo/123'
var noop = function() {}
window.XMLHttpRequest = function() {
global.location = url.parse(location)
global.XMLHttpRequest = function() {
this.open = noop
this.send = noop
this.withCredentials = false
@@ -20,17 +17,32 @@ var moduleName = require.resolve('../../')
delete require.cache[moduleName]
var http = require('../../')
test('Test simple url string', function(t) {
var url = { path: '/api/foo' }
var request = http.get(url, noop)
test('Make sure http object has correct properties', function (t) {
t.ok(http.Agent, 'Agent defined')
t.ok(http.ClientRequest, 'ClientRequest defined')
t.ok(http.ClientRequest.prototype, 'ClientRequest.prototype defined')
t.ok(http.IncomingMessage, 'IncomingMessage defined')
t.ok(http.IncomingMessage.prototype, 'IncomingMessage.prototype defined')
t.ok(http.METHODS, 'METHODS defined')
t.ok(http.STATUS_CODES, 'STATUS_CODES defined')
t.ok(http.get, 'get defined')
t.ok(http.globalAgent, 'globalAgent defined')
t.ok(http.request, 'request defined')
t.end()
})
t.equal( request._url, 'http://localhost:8081/api/foo', 'Url should be correct')
test('Test simple url string', function(t) {
var testUrl = { path: '/api/foo' }
var request = http.get(testUrl, noop)
var resolved = url.resolve(location, request._opts.url)
t.equal(resolved, 'http://localhost:8081/api/foo', 'Url should be correct')
t.end()
})
test('Test full url object', function(t) {
var url = {
var testUrl = {
host: "localhost:8081",
hostname: "localhost",
href: "http://localhost:8081/api/foo?bar=baz",
@@ -44,11 +56,11 @@ test('Test full url object', function(t) {
slashes: true
}
var request = http.get(url, noop)
var request = http.get(testUrl, noop)
t.equal( request._url, 'http://localhost:8081/api/foo?bar=baz', 'Url should be correct')
var resolved = url.resolve(location, request._opts.url)
t.equal(resolved, 'http://localhost:8081/api/foo?bar=baz', 'Url should be correct')
t.end()
})
test('Test alt protocol', function(t) {
@@ -61,37 +73,75 @@ test('Test alt protocol', function(t) {
var request = http.get(params, noop)
t.equal( request._url, 'foo://localhost:3000/bar', 'Url should be correct')
var resolved = url.resolve(location, request._opts.url)
t.equal(resolved, 'foo://localhost:3000/bar', 'Url should be correct')
t.end()
})
test('Test page with \'file:\' protocol', function (t) {
var params = {
hostname: 'localhost',
port: 3000,
path: '/bar'
}
var fileLocation = 'file:///home/me/stuff/index.html'
var normalLocation = global.location
global.location = url.parse(fileLocation) // Temporarily change the location
var request = http.get(params, noop)
global.location = normalLocation // Reset the location
var resolved = url.resolve(fileLocation, request._opts.url)
t.equal(resolved, 'http://localhost:3000/bar', 'Url should be correct')
t.end()
})
test('Test string as parameters', function(t) {
var url = '/api/foo'
var request = http.get(url, noop)
var testUrl = '/api/foo'
var request = http.get(testUrl, noop)
t.equal( request._url, 'http://localhost:8081/api/foo', 'Url should be correct')
var resolved = url.resolve(location, request._opts.url)
t.equal(resolved, 'http://localhost:8081/api/foo', 'Url should be correct')
t.end()
})
test('Test withCredentials param', function(t) {
var url = '/api/foo'
var request = http.get({ url: url, withCredentials: false }, noop)
t.equal( request._xhr.withCredentials, false, 'xhr.withCredentials should be false')
t.equal(request._xhr.withCredentials, false, 'xhr.withCredentials should be false')
var request = http.get({ url: url, withCredentials: true }, noop)
t.equal( request._xhr.withCredentials, true, 'xhr.withCredentials should be true')
t.equal(request._xhr.withCredentials, true, 'xhr.withCredentials should be true')
var request = http.get({ url: url }, noop)
t.equal( request._xhr.withCredentials, true, 'xhr.withCredentials should be true')
t.equal(request._xhr.withCredentials, false, 'xhr.withCredentials should be false')
t.end()
})
test('Test ipv6 address', function(t) {
var testUrl = 'http://[::1]:80/foo'
var request = http.get(testUrl, noop)
var resolved = url.resolve(location, request._opts.url)
t.equal(resolved, 'http://[::1]:80/foo', 'Url should be correct')
t.end()
})
test('Test relative path in url', function(t) {
var params = { path: './bar' }
var request = http.get(params, noop)
var resolved = url.resolve(location, request._opts.url)
t.equal(resolved, 'http://localhost:8081/foo/bar', 'Url should be correct')
t.end()
})
test('Cleanup', function (t) {
delete global.window
delete global.location
delete global.XMLHttpRequest
delete require.cache[moduleName]
t.end()
})
+32 -3
View File
@@ -1,3 +1,4 @@
var cookieParser = require('cookie-parser')
var basicAuth = require('basic-auth')
var express = require('express')
var fs = require('fs')
@@ -31,13 +32,17 @@ app.get('/testHeaders', function (req, res) {
})
res.setHeader('Content-Type', 'application/json')
res.setHeader('Cache-Control', 'no-cache')
// Request headers are sent in the body as json
var reqHeaders = {}
Object.keys(req.headers).forEach(function (key) {
key = key.toLowerCase()
if (key.indexOf('test-') === 0)
reqHeaders[key] = req.headers[key]
if (key.indexOf('test-') === 0) {
// different browsers format request headers with multiple values
// slightly differently, so normalize
reqHeaders[key] = req.headers[key].replace(', ', ',')
}
})
var body = JSON.stringify(reqHeaders)
@@ -46,6 +51,12 @@ app.get('/testHeaders', function (req, res) {
res.end()
})
app.get('/cookie', cookieParser(), function (req, res) {
res.setHeader('Content-Type', 'text/plain')
res.write('hello=' + req.cookies.hello)
res.end()
})
app.get('/auth', function (req, res) {
var user = basicAuth(req)
@@ -64,6 +75,24 @@ app.post('/echo', function (req, res) {
req.pipe(res)
})
app.use('/verifyEmpty', function (req, res) {
var empty = true
req.on('data', function (buf) {
if (buf.length > 0) {
empty = false
}
})
req.on('end', function () {
res.setHeader('Content-Type', 'text/plain')
if (empty) {
res.end('empty')
} else {
res.end('not empty')
}
})
})
app.use(function (req, res, next) {
var parsed = url.parse(req.url, true)
@@ -103,6 +132,6 @@ app.use(function (req, res, next) {
app.use(express.static(path.join(__dirname, 'static')))
var port = parseInt(process.env.ZUUL_PORT) || 8199
var port = parseInt(process.env.AIRTAP_SUPPORT_PORT) || 8199
console.log('Test server listening on port', port)
server.listen(port)
@@ -1,4 +1,3 @@
// TODO: send a PR to url to remove .trim() so this isn't necessary
if (!String.prototype.trim) {
(function() {
// Make sure we trim BOM and NBSP