Giter Club home page Giter Club logo

httpinvoke's Introduction

WARNING: instead of this project, you should use https://github.com/bitinn/node-fetch or https://github.com/github/fetch

httpinvoke

A no-dependencies HTTP client library for browsers and Node.js with a promise-based or Node.js-style callback-based API to progress events, text and binary file upload and download, partial response body, request and response headers, status code.

Build Status

Overview

  • Gracefully upgrades to latest platform-specific features:
  • Supports both NodeJS style callbacks and Promises/A+ (with progress events, see an example).
  • Supports transparent gzip/deflate content decoding.
  • Handles HTTP responses The Right Way™:
    • Tries hard to get the HTTP response status code in all cases.
    • Emits the HTTP response status code and headers as soon as they are available.
    • Gives you HTTP status code instead of an error, that is for example HTTP 404 would just return success, with status 404.
    • Throws an error only when the HTTP request did not actually completely finished.
  • Well tested - over 600 acceptance tests.
  • Detects the presence of CommonJS and AMD script loaders.
  • Supports npm, Bower and Component package managers.
  • Tested on these web browsers:
    • Internet Explorer 6 and later;
    • Firefox 3.0 and later;
    • Chrome 1 and later;
    • Safari 4.0 and later;
    • Opera 10.60 and later;
    • Android 2.3.3 and later;
    • Firefox OS 1.3 and later;
    • Kindle 3.0 (Version/4.0);
    • Samsung Smart-TV 4.5 (Webkit/537.42 Chromium/25.0).
  • Tested on these Node versions:
    • 0.8;
    • 0.10;
    • 0.11;
    • 0.12.

Installation

Install manually by adding to your HTML file:

<script src="/path/to/httpinvoke/httpinvoke-browser.js"></script>

Install with npm:

$ npm install --save httpinvoke

Install with component:

$ component install jakutis/httpinvoke

Install with bower:

$ bower install --save httpinvoke

Examples

Basic

var httpinvoke = require('httpinvoke');

httpinvoke('http://example.org', 'GET', function(err, body, statusCode, headers) {
    if(err) {
        return console.log('Failure', err);
    }
    console.log('Success', body, statusCode, headers);
});

Basic with Promises

var httpinvoke = require('httpinvoke');

httpinvoke('http://example.org', 'GET').then(function(res) {
    console.log('Success', res.body, res.statusCode, res.headers);
}, function(err) {
    console.log('Failure', err);
});

Promises and partial progress

httpinvoke('http://example.org', {
    partialOutputMode: 'chunked',
    outputType: 'bytearray'
}).then(function(res) {
    console.log('Success', res.body, res.statusCode, res.headers);
}, function(err) {
    console.log('Error occurred', err);
}, function(progress) {
    if(progress.type === 'upload') {
        // total and current are always defined
        console.log('progress: ' + (progress.total - progress.current) + ' bytes left to upload');
    } else if(progress.type === 'download') {
        var partialinfo = ' (received chunk of ' + progress.partial.length + ' bytes)';
        if(typeof progress.total === 'undefined') {
            console.log('progress: ' + progress.current + ' bytes downloaded' + partialinfo);
        } else {
            console.log('progress: ' + (progress.total - progress.current) + ' bytes left to download' + partialinfo);
        }
    } else if(progress.type === 'headers') {
        console.log('progress: received response with status code ' + progress.statusCode + ' and headers: ', progress.headers);
    }
});

Uploading an HTML form

var httpinvoke = require('httpinvoke');

var book = {
    content: 'Hello World',
    comment: 'initial version'
};
// convert the json object to application/x-www-form-urlencoded format string
var encodedBook = Object.keys(book).map(function(key) {
    return encodeURIComponent(key) + '=' + encodeURIComponent(book[key]);
}).join('&');
// upload to server
httpinvoke('http://example.org', 'POST', {
    headers: {
        // remove this header if doing a cross-domain request
        // or add a 'Content-Type' to 'Access-Control-Allow-Headers' server-side response header
        'Content-Type': 'application/x-www-form-urlencoded'
    },
    input: encodedBook
}, function(err) {
    if(err) {
        return console.log('Failure', err);
    }
    console.log('Success');
});

Downloading and uploading a file

var httpinvoke = require('httpinvoke');

var converters = {
    'text json': JSON.parse,
    'json text': JSON.stringify
};
httpinvoke('https://bower-component-list.herokuapp.com/', 'GET', {
    outputType: 'json',
    converters: converters
}).then(function(response) {
    console.log('There are ' + response.body.length + ' bower packages.');
    return httpinvoke('http://server.cors-api.appspot.com/server?id=9285649&enable=true&status=200&credentials=false&methods=POST', 'POST', {
        input: response.body,
        inputType: 'json',
        converters: converters
    });
}, function(err) {
    console.log('Error receiving package list', err);
}, function(progress) {
    console.log('Receiving package list progress', progress);
}).then(function(response) {
    console.log('Uploading finished', response);
}, function(err) {
    console.log('Error sending package list', err);
}, function(progress) {
    console.log('Sending package list progress', progress);
});

Streaming JSON

var clarinet = require('clarinet');
var httpinvoke = require('httpinvoke');

var parser = clarinet.parser();
var lastKey = null;
parser.onvalue = function(v) {
    if(lastKey === 'name') {
        console.log('component', v);
    }
};
parser.onopenobject = function(key) {
    lastKey = key;
};
parser.onkey = function(key) {
    lastKey = key;
};
// try with slow internet connection
httpinvoke('https://bower-component-list.herokuapp.com/', {
    partialOutputMode: 'chunked'
}).then(function(res) {
    parser.close();
    console.log('OK, in total there are ' + JSON.parse(res.body).length + ' bower components');
}, function(err) {
    console.log('Error occurred', err);
}, function(progress) {
    if(progress.type === 'download') {
        console.log('received chunk of length=' + progress.partial.length);
        parser.write(progress.partial);
    }
});

API

var abort = httpinvoke(url, method, options, cb)

Any one, two or three arguments can be skipped, except the url.

  • abort is a function for aborting the HTTP request. It is also a Promise/A+-compliant promise (has the then() method) that receives all the same events as the callbacks - uploading, downloading, gotStatus and finished - see an example. When invoked as a function, it immediately calls the "finished" callback with an Error. If "finished" callback is already called before the "abort", nothing happens.
  • url is a string for URL, e.g. "http://example.org/".
  • method is a string for HTTP method, one of "HEAD", "GET", "PATCH", "POST", "PUT", "DELETE".
  • options is an object for various options (see the Options section below) or a function, which is used as a "finished" option (see the first example).
  • cb is a function that is used as an option finished (read below).

Options

See the Examples section for all the options being used. All options are optional.

  • partialOutputMode is a string for the type of the partial argument of the downloading option, one of "disabled" (default, downloading will not receive the partial argument), "chunked" (the received value will be the latest chunk), "joined" (the received value will be the entire partial body).
  • anonymous - is a boolean - if true, then user credentials are not sent, if false - sent. Applicable only when anonymousOption feature flag is true. Defaults to the value of anonymousByDefault feature flag. See corsCredentials feature flag, if you are making a cross-origin request with system option being false.
  • system - is a boolean - if true, then same origin policy is not enforced, if false - enforced. Applicable only when systemOption feature flag is true. Defaults to the value of systemByDefault feature flag. If true, then overrides anonymous (makes it true).
  • timeout must be either one of:
    • undefined (default), means that finished must never be called with any of the timeout errors,
    • a number (greater than 0 and less than 1073741824) for maximum duration in milliseconds between the httpinvoke call and finished call, if it timeouts - finished must be called with "timeout" error,
    • an instance of Array (if corsFineGrainedTimeouts feature is not supported and url is cross-origin then timeout is assigned to the sum of this array) with elements: 0. a number (greater than 0 and less than 1073741824) for maximum duration in milliseconds between the httpinvoke call and gotStatus call, if it timeouts - finished must be called with "upload timeout" error; 0. a number (greater than 0 and less than 1073741824) for maximum duration in milliseconds between gotStatus call and finished call, if it timeouts - finished must be called with "download timeout" error.
  • uploading is a function that is called when HTTP request upload progress event happens. It is called with these arguments: 0. current is a number for the number of bytes currently sent; 0. total is a number for the total number of bytes to be sent.
  • downloading is a function that is called when HTTP response download progress event happens. It is called with these arguments: 0. current is a number for the number of bytes currently received; 0. total is a number for the total number of bytes to be received, or undefined if not known. 0. partial is a string (or bytearray, if either outputType is "bytearray" or a custom outputType will be converted from bytearray), or undefined if partialOutputMode option is set to "disabled".
  • gotStatus is a function that is called when HTTP response headers are received. It is called with these arguments: 0. status is a number for an HTTP response status code, either undefined, or a correct value. 0. headers is an object for HTTP response headers. Keys are lower-cased header names, values are strings.
  • finished is a function that is called when HTTP response is fully downloaded, or any error happens. It is called with these arguments: 0. err is null or an object that is an instance of Error, please read error conditions section below; 0. output is: * undefined, if err is not null or no response entity has been received (e.g. when method is "HEAD"), * a string, if outputType is "text", * a bytearray - instance of Uint8Array or instance of Buffer or instance of Array that has elements of type "number" with values ranging from 0 to 255 - if outputType is "bytearray", * any value - if outputType is neither "bytearray", nor "text", i.e. a converter has been called. 0. status is: * undefined, if err is not null or no correct value is available, * otherwise, a number for an HTTP response status code, correct value. 0. headers is: * undefined, if err is not null, * otherwise, an object for HTTP response headers. Keys are lower-cased header names, values are strings.
  • outputType is a string for the type of the output argument of the finished option, one of "text" (default), "bytearray" or a custom value that has corresponding converters.
  • inputType is a string for the type of the input option, one of "auto"(default), "bytearray", "text", "formdata" or a custom value that has corresponding converters. If not "auto", input must be not be undefined.
  • input must be either one of:
    • undefined (default), if inputType is "auto" and request headers does not have Content-Type,
    • a string, if inputType is "text" or "auto",
    • a bytearray - instance of ArrayBuffer or instance of ArrayBufferView or instance of Blob or instance of File or instance of Buffer or instance of Array that has elements of type "number" with values ranging from 0 to 255 - if inputType is "bytearray" or "auto".
    • an instance of FormData - if inputType is "formdata" or "auto",
  • headers is an object for HTTP request headers. Keys are header names, values are strings.
  • converters is an object to convert custom inputType and outputType values to "bytearray" or "text". Example: {"json text": JSON.stringify, "text json": JSON.parse}. If you use custom inputType, then there must be at least one converter from that type to "text" or "bytearray", and the other way around for outputType.
  • corsExposedHeaders is an array of HTTP response headers to be extracted in gotStatus call. Default simple headers like "Content-Type" are always extracted. Applicable only for cross-origin requests.
  • corsOriginHeader is a string for the request header name for browsers with buggy CORS implementations (e.g. Android Browser 2.3.7) - which do not send the Origin request header in actual request. By default corsOriginHeader is not set, as it needs a proper Access-Control-Allow-Headers server-side header, see dummyserver.js for an example of server-side part of the workaround implementation.

Callback Sequence

The callbacks are called in this strict sequence:

  1. uploading (two or more times);
  2. gotStatus (one time);
  3. downloading (two or more times);
  4. finished (one time), except the case that finished can be called with Error any time, and then no callbacks will be called.

Static Methods

.hook(type, hook)

hook is a function to hook into how httpinvoke works. It leaves the current instance of httpinvoke untouched. It returns a new instance of httpinvoke with a new hook, and all the hooks inherited from the old httpinvoke. It takes these arguments:

  • type - a string, one of 'uploading', 'gotStatus', 'downloading', 'finished'
  • hook - a function that is called with arguments in accordance with what type defines. It must return an array of the same (or modified) arguments which is passed to the next hook that is added after the current (of the same type).
// a new httpinvoke with a hook to fail on 4xx and 5xx statuses, just like jQuery
httpinvoke = httpinvoke.hook('finished', function(err, output, statusCode, headers) {
    if(err) {
        return arguments;
    }
    if(typeof statusCode === 'undefined') {
        return [new Error('Server or client error - undefined HTTP status'), output, statusCode, headers];
    }
    if(statusCode >= 400 && statusCode <= 599) {
        return [new Error('Server or client error - HTTP status ' + statusCode), output, statusCode, headers];
    }
    return arguments;
});

// err will be set; output, status, headers are unchanged
httpinvoke('http://example.org/foobar', function(err, output, statusCode, headers) {
    console.log(err, output, statusCode, headers);
});

// will be rejected (prints 'Failure with status=404')
httpinvoke('http://example.org/foobar').then(function(res) {
    console.log('Success', res.body, res.statusCode, res.headers);
}, function(err, res) {
    console.log('Failure with status=', res.statusCode);
});

Feature Flags

There are feature flags to be queried for platform-specific features.

if(httpinvoke.cors) {
    console.log('Cross-Origin Resource Sharing support is available!');
}

Error Conditions

The finished callback will be called with an instance of Error only when strictly either one of these things happen:

  • abort function was called (error message "abort");
  • number of received bytes does not equal the Content-Type value or native XMLHttpRequest called .onerror without a .status or .statusText (error message "network error") - this can happen due to various network errors, server response sending or request receiving errors, or simply an unsupported status code - e.g. Firefox 3.0 ends up here after a status 408 response;
  • sending request timed out (error message "upload timeout");
  • receiving response timed out (error message "download timeout");
  • sending request and receiving response timed out (error message "timeout");
  • converter from converters option threw an error (the thrown error message is passed);
  • request did not even start (error message "Error code #%. See https://github.com/jakutis/httpinvoke#error-codes") - calling options validation failed or a feature that is not supported by the platform was used (various error messages are passed, e.g. "Unsupported method TRACE"). See error codes.

So generally, finishing with an error means that a response has not been received. Please note that a request can finish successfully, with an err set to null, but also with an undefined status, undefined output and an empty headers object - generally status is defined at all times, but some older browsers provide status code only for 2XX responses - e.g. Opera 11.50.

Error Codes

  • 001 Option % is less than zero
  • 002 Option % is not a number
  • 003 Option "partialOutputMode" is neither "disabled", nor "chunked", nor "joined"
  • 004 Unsupported method %
  • 005 Unsupported outputType %
  • 006 "inputType" is undefined or auto and input is neither string, nor FormData, nor an instance of Buffer, nor Blob, nor File, nor ArrayBuffer, nor ArrayBufferView, nor Int8Array, nor Uint8Array, nor Uint8ClampedArray, nor Int16Array, nor Uint16Array, nor Int32Array, nor Uint32Array, nor Float32Array, nor Float64Array, nor Array
  • 007 "inputType" is text, but input is not a string
  • 008 "inputType" is formdata, but input is not an instance of FormData
  • 009 "inputType" is bytearray, but input is neither an instance of Buffer, nor Blob, nor File, nor ArrayBuffer, nor ArrayBufferView, nor Int8Array, nor Uint8Array, nor Uint8ClampedArray, nor Int16Array, nor Uint16Array, nor Int32Array, nor Uint32Array, nor Float32Array, nor Float64Array, nor Array
  • 010 There is no converter for specified "inputType" %
  • 011 "input" is undefined, but "inputType" is defined
  • 012 "input" is undefined, but "Content-Type" input header is defined
  • 013 "timeout" value is not valid
  • 014 Input header % is forbidden to be set programmatically
  • 015 Input header % (to be precise, all Proxy-*) is forbidden to be set programmatically
  • 016 Input header % (to be precise, all Sec-*) is forbidden to be set programmatically
  • 017 bytearray inputType is not supported on this platform, please always test using requestTextOnly feature flag - hint - you may want to try sending FormData (formdata type)
  • 018 Cross-origin requests are not supported in this browser
  • 019 % method cross-origin requests are not supported in this browser
  • 020 PATCH method requests are not supported in this browser
  • 021 Unable to construct XMLHttpRequest object
  • 022 Unable to open uri %
  • 023 Unable to set input header %
  • 024 Unable to send
  • 025 "%" protocol is not among these supported protocols: %
  • 026 Given URL "%" is relative, but relativeURLs flag is false

Development

After cloning the repo, and on each modification of src folder, you have to run npm run compile.

To test for NodeJS functionality run npm run test-node.

To test for web browser functionality run npm run test-browser, copy the URL link that is displayed in console and open it in any web browser.

Checklist before releasing

  • npm test
  • tests pass on the browser versions indicated in overview.
  • package.json, bower.json and component.json version number bumped
  • npm run compile
  • release X.X.X commit created and tagged as X.X.X
  • npm publish

httpinvoke's People

Contributors

bpierre avatar bvalosek avatar groner avatar jakutis avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

httpinvoke's Issues

Support OPTIONS method

It's pretty common to use OPTIONS to snag the Allow header for a resource. Currently httpinvoke fails this invocation with a error #4, unsupported method.
Can this be added?

Refused to get unsafe header "Content-Length"

I am wondering whether you can help me resolve the following issue. I am using the library for a CORS request. The request completed successfully but i am getting the following errors on the console.

Refused to get unsafe header "Content-Length" ozmeter-plugin.js:652
Refused to get unsafe header "Content-Range" ozmeter-plugin.js:652
Refused to get unsafe header "Content-Encoding" ozmeter-plugin.js:652
Refused to get unsafe header "Content-Length" ozmeter-plugin.js:652
Refused to get unsafe header "Content-Range" ozmeter-plugin.js:652
Refused to get unsafe header "Content-Encoding" ozmeter-plugin.js:652
Refused to get unsafe header "Content-Length" ozmeter-plugin.js:652
Refused to get unsafe header "Content-Range" ozmeter-plugin.js:652
Refused to get unsafe header "Content-Encoding" ozmeter-plugin.js:652

Here is the response from the server so i am surprised i am getting some of these errors.

HTTP/1.1 200 OK
Server: nginx/1.4.1 (Ubuntu)
Date: Sun, 16 Feb 2014 23:58:09 GMT
Content-Type: application/json; charset=utf-8
Content-Length: 113
Connection: keep-alive
X-Powered-By: Express
Access-Control-Allow-Origin: *
Access-Control-Allow-Methods: *
Access-Control-Allow-Headers: Content-Type, Content-Length
Set-Cookie: connect.sid=s%3Alfo7rNMw3hF1NR29Tt6bWIOw.J8NKsC9wHqaMDQNfYaqO5W0oo%2FUkGhbsIZpk7y4%2FAL8; Path=/; HttpOnly

Any clues?

Can be used for file upload in browser?

Docs are saying that binary uploads are supported. Wondering can this be used for file upload and if so, how that would work in ie9 - I did not found any iframe fallback for file upload?

Thanks!

How to add `mozSystem` to XHR object?

Mozilla has two custom extensions which can be added to the XMLHttpRequest object: mozSystem and mozAnon.

mozSystem is particularly useful because it disables the Same Origin Policy on Firefox OS applications. This allows FFxOS mobile devices to avoid preflight OPTIONS requests. The full documentation on this property states:

Setting this flag to true allows making cross-site connections without requiring the server to opt-in using CORS.

https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest

Is there a way to add mozSystem: true to the underlying XMLHttpRequest object built up by httpinvoke?

Promise chain stalls

In the following example, the first promise resolves but not the ones after it do not. I'm using release 1.1.3.

var httpinvoke = require('httpinvoke');

httpinvoke('http://httpbin.org/get')
  .then(function(res) { console.log(res.statusCode) })
  .then(function() { console.log('working pretty hard!') })
  .then(null, function(e) { console.error('ohno', e) });

Wrapping the original promise with Q() results in the expected behavior.

var httpinvoke = require('httpinvoke'),
    Q = require('q');

Q(httpinvoke('http://httpbin.org/get'))
  .then(function(res) { console.log(res.statusCode) })
  .then(function() { console.log('working pretty hard!') })
  .then(null, function(e) { console.error('ohno', e) });

Why is User-Agent header forbidden in node ?

I think the two next lines are not needed when using the node.js version of this library.
This protection seems only related to XmlHTTPRequest calls and not from those performed using https://nodejs.org/api/http.html

// http://www.w3.org/TR/XMLHttpRequest/#the-setrequestheader()-method
var forbiddenInputHeaders = ['accept-charset', 'accept-encoding', 'access-control-request-headers', 'access-control-request-method', 'connection', 'content-length', 'content-transfer-encoding', 'cookie', 'cookie2', 'date', 'dnt', 'expect', 'host', 'keep-alive', 'origin', 'referer', 'te', 'trailer', 'transfer-encoding', 'upgrade', 'user-agent', 'via'];

Reject promise on 4xx or 5xx

I would like to have a rejected promise returned when the HTTP response is a 404 or similar. Am I missing something or is currently the only way to do that to check the res.statusCode for 4xx and 5xx, then create a new promise, reject it immediately and return that?

Would be nice if we could set something in the options object like:

  • failOn is an array of HTTP status codes on which err is set when using callbacks or the promise rejected respectively. Allows strings of the form "4xx".

node: HTTPS urls use HTTP instead

The node version of httpinvoke ignores "https" in URLs and connects to plain http instead.

For example,

var httpinvoke = require('httpinvoke');

httpinvoke('https://www.eff.org', 'GET', function (err, body, status, headers) {
  console.log(status + ': ' + JSON.stringify(headers) + '\n\n' + body);
});

returns a redirect to the https url instead of the correct https content.

The code in src/node.js needs to be modified to do something like

var httpOrHttps = uri.protocol === 'https:' ? https : http; // uri.protocol is always lowercase
var req = httpOrHttps.request({

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.