GithubHelp home page GithubHelp logo

tomas / needle Goto Github PK

View Code? Open in Web Editor NEW
1.6K 1.6K 236.0 1.12 MB

Nimble, streamable HTTP client for Node.js. With proxy, iconv, cookie, deflate & multipart support.

Home Page: https://www.npmjs.com/package/needle

License: MIT License

JavaScript 76.82% HTML 23.18%

needle's Introduction

Needle

NPM

npm downloads Build status

The leanest and most handsome HTTP client in the Nodelands.

var needle = require('needle');

needle.get('http://www.google.com', function(error, response) {
  if (!error && response.statusCode == 200)
    console.log(response.body);
});

Callbacks not floating your boat? Needle got your back.

var data = {
  file: '/home/johnlennon/walrus.png',
  content_type: 'image/png'
};

// the callback is optional, and needle returns a `readableStream` object
// that triggers a 'done' event when the request/response process is complete.
needle
  .post('https://my.server.com/foo', data, { multipart: true })
  .on('readable', function() { /* eat your chunks */ })
  .on('done', function(err) {
    console.log('Ready-o!');
  })

From version 2.0.x up, Promises are also supported. Just call needle() directly and you'll get a native Promise object.

needle('put', 'https://hacking.the.gibson/login', { password: 'god' }, { json: true })
  .then(function(response) {
    return doSomethingWith(response)
  })
  .catch(function(err) {
    console.log('Call the locksmith!')
  })

With only two real dependencies, Needle supports:

  • HTTP/HTTPS requests, with the usual verbs you would expect
  • All of Node's native TLS options, such as 'rejectUnauthorized' (see below)
  • Basic & Digest authentication with auto-detection
  • Multipart form-data (e.g. file uploads)
  • HTTP Proxy forwarding, optionally with authentication
  • Streaming gzip, deflate, and brotli decompression
  • Automatic XML & JSON parsing
  • 301/302/303 redirect following, with fine-grained tuning, and
  • Streaming non-UTF-8 charset decoding, via iconv-lite
  • Aborting any or all Needle requests using AbortSignal objects

And yes, Mr. Wayne, it does come in black.

This makes Needle an ideal alternative for performing quick HTTP requests in Node, either for API interaction, downloading or uploading streams of data, and so on.

Install

$ npm install needle

Usage

// using promises
needle('get', 'https://server.com/posts/123')
  .then(function(resp) {
    // ...
  })
  .catch(function(err) {
    // ...
  });

// with callback
needle.get('ifconfig.me/all.json', function(error, response, body) {
  if (error) throw error;

  // body is an alias for `response.body`,
  // that in this case holds a JSON-decoded object.
  console.log(body.ip_addr);
});

// no callback, using streams
needle.get('https://google.com/images/logo.png')
  .pipe(fs.createWriteStream('logo.png'))
  .on('done', function(err) {
    console.log('Pipe finished!');
  });

As you can see, you can use Needle with Promises or without them. When using Promises or when a callback is passed, the response's body will be buffered and written to response.body, and the callback will be fired when all of the data has been collected and processed (e.g. decompressed, decoded and/or parsed).

When no callback is passed, however, the buffering logic will be skipped but the response stream will still go through Needle's processing pipeline, so you get all the benefits of post-processing while keeping the streamishness we all love from Node.

Response pipeline

Depending on the response's Content-Type, Needle will either attempt to parse JSON or XML streams, or, if a text response was received, will ensure that the final encoding you get is UTF-8.

You can also request a gzip/deflated/brotli response, which, if sent by the server, will be processed before parsing or decoding is performed. (Note: brotli is only supported on Node 10.16.0 or above, and will not be requested or processed on earlier versions.)

needle.get('http://stackoverflow.com/feeds', { compressed: true }, function(err, resp) {
  console.log(resp.body); // this little guy won't be a Gzipped binary blob
                          // but a nice object containing all the latest entries
});

Or in anti-callback mode, using a few other options:

var options = {
  compressed         : true, // sets 'Accept-Encoding' to 'gzip, deflate, br'
  follow_max         : 5,    // follow up to five redirects
  rejectUnauthorized : true  // verify SSL certificate
}

var stream = needle.get('https://backend.server.com/everything.html', options);

// read the chunks from the 'readable' event, so the stream gets consumed.
stream.on('readable', function() {
  while (data = this.read()) {
    console.log(data.toString());
  }
})

stream.on('done', function(err) {
  // if our request had an error, our 'done' event will tell us.
  if (!err) console.log('Great success!');
})

API

needle(method, url[, data][, options][, callback]) (> 2.0.x)

Calling needle() directly returns a Promise. Besides method and url, all parameters are optional, although when sending a post, put or patch request you will get an error if data is not present.

needle('get', 'http://some.url.com')
  .then(function(resp) { console.log(resp.body) })
  .catch(function(err) { console.error(err) })

Except from the above, all of Needle's request methods return a Readable stream, and both options and callback are optional. If passed, the callback will return three arguments: error, response and body, which is basically an alias for response.body.

needle.head(url[, options][, callback])

needle.head('https://my.backend.server.com', {
  open_timeout: 5000 // if we're not able to open a connection in 5 seconds, boom.
}, function(err, resp) {
  if (err)
    console.log('Shoot! Something is wrong: ' + err.message)
  else
    console.log('Yup, still alive.')
})

needle.get(url[, options][, callback])

needle.get('google.com/search?q=syd+barrett', function(err, resp) {
  // if no http:// is found, Needle will automagically prepend it.
});

needle.post(url, data[, options][, callback])

var options = {
  headers: { 'X-Custom-Header': 'Bumbaway atuna' }
}

needle.post('https://my.app.com/endpoint', 'foo=bar', options, function(err, resp) {
  // you can pass params as a string or as an object.
});

needle.put(url, data[, options][, callback])

var nested = {
  params: {
    are: {
      also: 'supported'
    }
  }
}

needle.put('https://api.app.com/v2', nested, function(err, resp) {
  console.log('Got ' + resp.bytes + ' bytes.') // another nice treat from this handsome fella.
});

needle.patch(url, data[, options][, callback])

Same behaviour as PUT.

needle.delete(url, data[, options][, callback])

var options = {
  username: 'fidelio',
  password: 'x'
}

needle.delete('https://api.app.com/messages/123', null, options, function(err, resp) {
  // in this case, data may be null, but you need to explicity pass it.
});

needle.request(method, url, data[, options][, callback])

Generic request. This not only allows for flexibility, but also lets you perform a GET request with data, in which case will be appended to the request as a query string, unless you pass a json: true option (read below).

var params = {
  q    : 'a very smart query',
  page : 2
}

needle.request('get', 'forum.com/search', params, function(err, resp) {
  if (!err && resp.statusCode == 200)
    console.log(resp.body); // here you go, mister.
});

Now, if you set pass json: true among the options, Needle won't set your params as a querystring but instead send a JSON representation of your data through the request's body, as well as set the Content-Type and Accept headers to application/json.

needle.request('get', 'forum.com/search', params, { json: true }, function(err, resp) {
  if (resp.statusCode == 200) console.log('It worked!');
});

Events

The Readable stream object returned by the above request methods emits the following events, in addition to the regular ones (e.g. end, close, data, pipe, readable).

Event: 'response'

  • response <http.IncomingMessage>

Emitted when the underlying http.ClientRequest emits a response event. This is after the connection is established and the header received, but before any of it is processed (e.g. authorization required or redirect to be followed). No data has been consumed at this point.

Event: 'redirect'

  • location <String>

Indicates that the a redirect is being followed. This means that the response code was a redirect (301, 302, 303, 307) and the given redirect options allowed following the URL received in the Location header.

Event: 'header'

  • statusCode <Integer>
  • headers <Object>

Triggered after the header has been processed, and just before the data is to be consumed. This implies that no redirect was followed and/or authentication header was received. In other words, we got a "valid" response.

Event: 'done' (previously 'end')

  • exception <Error> (optional)

Emitted when the request/response process has finished, either because all data was consumed or an error ocurred somewhere in between. Unlike a regular stream's end event, Needle's done will be fired either on success or on failure, which is why the first argument may be an Error object. In other words:

var resp = needle.get('something.worthy/of/being/streamed/by/needle');
resp.pipe(someWritableStream);

resp.on('done', function(err) {
  if (err) console.log('An error ocurred: ' + err.message);
  else console.log('Great success!');
})

Event: 'err'

  • exception <Error>

Emitted when an error ocurrs. This should only happen once in the lifecycle of a Needle request.

Event: 'timeout'

  • type <String>

Emitted when an timeout error occurs. Type can be either 'open', 'response', or 'read'. This will called right before aborting the request, which will also trigger an err event, a described above, with an ECONNRESET (Socket hang up) exception.

Request options

For information about options that've changed, there's always the changelog.

  • agent : Uses an http.Agent of your choice, instead of the global, default one. Useful for tweaking the behaviour at the connection level, such as when doing tunneling (see below for an example).
  • json : When true, sets content type to application/json and sends request body as JSON string, instead of a query string.
  • open_timeout: (or timeout) Returns error if connection takes longer than X milisecs to establish. Defaults to 10000 (10 secs). 0 means no timeout.
  • response_timeout: Returns error if no response headers are received in X milisecs, counting from when the connection is opened. Defaults to 0 (no response timeout).
  • read_timeout: Returns error if data transfer takes longer than X milisecs, once response headers are received. Defaults to 0 (no timeout).
  • follow_max : (or follow) Number of redirects to follow. Defaults to 0. See below for more redirect options.
  • multipart : Enables multipart/form-data encoding. Defaults to false. Use it when uploading files.
  • proxy : Forwards request through HTTP(s) proxy. Eg. proxy: 'http://user:[email protected]:3128'. For more advanced proxying/tunneling use a custom agent, as described below.
  • headers : Object containing custom HTTP headers for request. Overrides defaults described below.
  • auth : Determines what to do with provided username/password. Options are auto, digest or basic (default). auto will detect the type of authentication depending on the response headers.
  • stream_length: When sending streams, this lets you manually set the Content-Length header --if the stream's bytecount is known beforehand--, preventing ECONNRESET (socket hang up) errors on some servers that misbehave when receiving payloads of unknown size. Set it to 0 and Needle will get and set the stream's length for you, or leave unset for the default behaviour, which is no Content-Length header for stream payloads.
  • localAddress: , IP address. Passed to http/https request. Local interface from which the request should be emitted.
  • uri_modifier: Anonymous function taking request (or redirect location if following redirects) URI as an argument and modifying it given logic. It has to return a valid URI string for successful request.
  • signal : An AbortSignal object that can be used to abort any or all Needle requests.

Response options

  • decode_response : (or decode) Whether to decode the text responses to UTF-8, if Content-Type header shows a different charset. Defaults to true.
  • parse_response : (or parse) Whether to parse XML or JSON response bodies automagically. Defaults to true. You can also set this to 'xml' or 'json' in which case Needle will only parse the response if the content type matches.
  • output : Dump response output to file. This occurs after parsing and charset decoding is done.
  • parse_cookies : Whether to parse response’s Set-Cookie header. Defaults to true. If parsed, response cookies will be available at resp.cookies.

HTTP Header options

These are basically shortcuts to the headers option described above.

  • cookies : Builds and sets a Cookie header from a { key: 'value' } object.
  • compressed: If true, sets 'Accept-Encoding' header to 'gzip,deflate', and inflates content if zipped. Defaults to false.
  • username : For HTTP basic auth.
  • password : For HTTP basic auth. Requires username to be passed, but is optional.
  • accept : Sets 'Accept' HTTP header. Defaults to */*.
  • connection: Sets 'Connection' HTTP header. Not set by default, unless running Node < 0.11.4 in which case it defaults to close. More info about this below.
  • user_agent: Sets the 'User-Agent' HTTP header. Defaults to Needle/{version} (Node.js {node_version}).
  • content_type: Sets the 'Content-Type' header. Unset by default, unless you're sending data in which case it's set accordingly to whatever is being sent (application/x-www-form-urlencoded, application/json or multipart/form-data). That is, of course, unless the option is passed, either here or through options.headers. You're the boss.

Node.js TLS Options

These options are passed directly to https.request if present. Taken from the original documentation:

  • pfx : Certificate, Private key and CA certificates to use for SSL.
  • key : Private key to use for SSL.
  • passphrase : A string of passphrase for the private key or pfx.
  • cert : Public x509 certificate to use.
  • ca : An authority certificate or array of authority certificates to check the remote host against.
  • ciphers : A string describing the ciphers to use or exclude.
  • rejectUnauthorized : If true, the server certificate is verified against the list of supplied CAs. An 'error' event is emitted if verification fails. Verification happens at the connection level, before the HTTP request is sent.
  • secureProtocol : The SSL method to use, e.g. SSLv3_method to force SSL version 3.
  • family : IP address family to use when resolving host and hostname. Valid values are 4 or 6. When unspecified, both IP v4 and v6 will be used.

Redirect options

These options only apply if the follow_max (or follow) option is higher than 0.

  • follow_set_cookies : Sends the cookies received in the set-cookie header as part of the following request, if hosts match. false by default.
  • follow_set_referer : Sets the 'Referer' header to the requested URI when following a redirect. false by default.
  • follow_keep_method : If enabled, resends the request using the original verb instead of being rewritten to get with no data. false by default.
  • follow_if_same_host : When true, Needle will only follow redirects that point to the same host as the original request. false by default.
  • follow_if_same_protocol : When true, Needle will only follow redirects that point to the same protocol as the original request. false by default.
  • follow_if_same_location : Unless true, Needle will not follow redirects that point to same location (as set in the response header) as the original request URL. false by default.

Overriding Defaults

Yes sir, we have it. Needle includes a defaults() method, that lets you override some of the defaults for all future requests. Like this:

needle.defaults({
  open_timeout: 60000,
  user_agent: 'MyApp/1.2.3',
  parse_response: false });

This will override Needle's default user agent and 10-second timeout, and disable response parsing, so you don't need to pass those options in every other request.

More advanced Proxy support

Since you can pass a custom HTTPAgent to Needle you can do all sorts of neat stuff. For example, if you want to use the tunnel module for HTTPS proxying, you can do this:

var tunnel = require('tunnel');
var myAgent = tunnel.httpOverHttp({
  proxy: { host: 'localhost' }
});

needle.get('foobar.com', { agent: myAgent });

Otherwise, you can use the hpagent package, which keeps the internal sockets alive to be reused.

const { HttpsProxyAgent } = require('hpagent');
needle('get', 'https://localhost:9200', {
  agent: new HttpsProxyAgent({
    keepAlive: true,
    keepAliveMsecs: 1000,
    maxSockets: 256,
    maxFreeSockets: 256,
    scheduling: 'lifo',
    proxy: 'https://localhost:8080'
  })
});

Regarding the 'Connection' header

Unless you're running an old version of Node (< 0.11.4), by default Needle won't set the Connection header on requests, yielding Node's default behaviour of keeping the connection alive with the target server. This speeds up immensely the process of sending several requests to the same host.

On older versions, however, this has the unwanted behaviour of preventing the runtime from exiting, either because of a bug or 'feature' that was changed on 0.11.4. To overcome this Needle does set the 'Connection' header to 'close' on those versions, however this also means that making new requests to the same host doesn't benefit from Keep-Alive.

So if you're stuck on 0.10 or even lower and want full speed, you can simply set the Connection header to 'Keep-Alive' by using { connection: 'Keep-Alive' }. Please note, though, that an event loop handler will prevent the runtime from exiting so you'll need to manually call process.exit() or the universe will collapse.

By default, Node uses http.globalAgent with keepAlive option set to false to send HTTP(s) requests. That's why, by default, "Connection: close" header is sent, and the Connection is destroyed after the request.

To keep the Connection alive, you should create http(s).Agent with keepAlive: true and pass it as request option:

const keepAliveAgent = new require('https').Agent({ keepAlive: true, keepAliveMsecs: 10000 });
needle(method, url, data, { agent: keepAliveAgent })
    .then(function(response) {})

Examples Galore

HTTPS GET with Basic Auth

needle.get('https://api.server.com', { username: 'you', password: 'secret' },
  function(err, resp) {
    // used HTTP auth
});

Or use RFC-1738 basic auth URL syntax:

needle.get('https://username:[email protected]', function(err, resp) {
    // used HTTP auth from URL
});

Digest Auth

needle.get('other.server.com', { username: 'you', password: 'secret', auth: 'digest' },
  function(err, resp, body) {
    // needle prepends 'http://' to your URL, if missing
});

Custom Accept header, deflate

var options = {
  compressed : true,
  follow     : 10,
  accept     : 'application/vnd.github.full+json'
}

needle.get('api.github.com/users/tomas', options, function(err, resp, body) {
  // body will contain a JSON.parse(d) object
  // if parsing fails, you'll simply get the original body
});

GET XML object

needle.get('https://news.ycombinator.com/rss', function(err, resp, body) {
  // you'll get a nice object containing the nodes in the RSS
});

GET binary, output to file

needle.get('http://upload.server.com/tux.png', { output: '/tmp/tux.png' }, function(err, resp, body) {
  // you can dump any response to a file, not only binaries.
});

GET through proxy

needle.get('http://search.npmjs.org', { proxy: 'http://localhost:1234' }, function(err, resp, body) {
  // request passed through proxy
});

GET a very large document in a stream (from 0.7+)

var stream = needle.get('http://www.as35662.net/100.log');

stream.on('readable', function() {
  var chunk;
  while (chunk = this.read()) {
    console.log('got data: ', chunk);
  }
});

GET JSON object in a stream (from 0.7+)

var stream = needle.get('http://jsonplaceholder.typicode.com/db', { parse: true });

stream.on('readable', function() {
  var node;

  // our stream will only emit a single JSON root node.
  while (node = this.read()) {
    console.log('got data: ', node);
  }
});

GET JSONStream flexible parser with search query (from 0.7+)

 // The 'data' element of this stream will be the string representation
 // of the titles of all posts.

needle.get('http://jsonplaceholder.typicode.com/db', { parse: true })
      .pipe(new JSONStream.parse('posts.*.title'));
      .on('data', function (obj) {
        console.log('got post title: %s', obj);
      });

File upload using multipart, passing file path

var data = {
  foo: 'bar',
  image: { file: '/home/tomas/linux.png', content_type: 'image/png' }
}

needle.post('http://my.other.app.com', data, { multipart: true }, function(err, resp, body) {
  // needle will read the file and include it in the form-data as binary
});

Stream upload, PUT or POST

needle.put('https://api.app.com/v2', fs.createReadStream('myfile.txt'), function(err, resp, body) {
  // stream content is uploaded verbatim
});

Multipart POST, passing data buffer

var buffer = fs.readFileSync('/path/to/package.zip');

var data = {
  zip_file: {
    buffer       : buffer,
    filename     : 'mypackage.zip',
    content_type : 'application/octet-stream'
  }
}

needle.post('http://somewhere.com/over/the/rainbow', data, { multipart: true }, function(err, resp, body) {
  // if you see, when using buffers we need to pass the filename for the multipart body.
  // you can also pass a filename when using the file path method, in case you want to override
  // the default filename to be received on the other end.
});

Multipart with custom Content-Type

var data = {
  token: 'verysecret',
  payload: {
    value: JSON.stringify({ title: 'test', version: 1 }),
    content_type: 'application/json'
  }
}

needle.post('http://test.com/', data, { timeout: 5000, multipart: true }, function(err, resp, body) {
  // in this case, if the request takes more than 5 seconds
  // the callback will return a [Socket closed] error
});

For even more examples, check out the examples directory in the repo.

Testing

To run tests, you need to generate a self-signed SSL certificate in the test directory. After cloning the repository, run the following commands:

$ mkdir -p test/keys
$ openssl genrsa -out test/keys/ssl.key 2048
$ openssl req -new -key test/keys/ssl.key -x509 -days 999 -out test/keys/ssl.cert

Then you should be able to run npm test once you have the dependencies in place. To run the tests with debug logs, set the environment variable NODE_DEBUG to needle (for example, by running NODE_DEBUG=needle npm test).

Note: Tests currently only work on linux-based environments that have /proc/self/fd. They do not work on MacOS environments. You can use Docker to run tests by creating a container and mounting the needle project directory on /app

docker create --name Needle -v $(pwd) -w /app -v $(pwd)/node_modules -i node:argon

Or alternatively:

docker run -it -w /app --name Needle \
--mount type=bind,source="$(pwd)",target=/app \
node:fermium bash

Credits

Written by Tomás Pollak, with the help of contributors. If Needle's of any help to you, please consider supporting its development!

ko-fi

Copyright

(c) Fork Ltd. Licensed under the MIT license.

needle's People

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  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

needle's Issues

Needle parses in streams mode

Needle still parses JSON and XML data, even when no callback is provided. This causes the stream to be in object mode.

Is this expected behavior? In my mind, parsing should happen in streams mode. (But obviously this can trivially be disabled by passing in parsed: false)

about DEBUG environment

When I'm using the 'debug' module which should set the DEBUG environment a value,'needle' module would output many debugging messages.So I can't find the debugging messages I want.

Sorry, my english is not well, so I don't know if you can understand.

Boolean data in POST request gets converted as string

Hi tomas,
Sending boolean data as part of JSON data becomes string on the receiving side.
For Eg: data = {id:100, fname:'john', status:{isactive:true}}
here isactive:true becomes isactive:'true' <- string 'true'

Is this an expected behaviour or bug?

In, lib/needle.js lines 110 & 111

    post_data = (typeof(data) === "string") ? data : stringify(data);
    config.headers['Content-Type'] = 'application/x-www-form-urlencoded';

to make this work, if I change like following it works as expected

    post_data = (typeof(data) === "string") ? data : JSON.stringify(data);
    config.headers['Content-Type'] = 'application/json';

If bug, Can you please fix this?.

Unexpected behaviour

If .post() method is called with empty data object and multipart option as true then in callback function err param will be null and resp will be [Error: Empty multipart body. Invalid data.]

Example:

needle.post('http://localhost', {}, { multipart: true }, function(err, resp, body) {
  console.log(err); // null
  console.log(resp); // [Error: Empty multipart body. Invalid data.]
});

Mocha is reporting global leaks in needle

I was using needle in my app and CI was reporting Error: global leaks detected: self, request_opts, protocol. On drilling down it appears to be originating from needle module. Switched to request and it's fine now.

Decoder output garbled text on occasions

We are running into issues where some part of the response body are garbled upon decoding, and more annoyingly it does not always happen to the same page, not even the same text. So it's difficult for us to determine whether this is problem for needle or iconv-lite.

For example: http://www.huanqiukexue.com/html/newgc/2014/1215/25011.html is a page with charset gb2312, and upon multiple fetch we see different result on decode.

(text between question mark symbol are garbled, hopefully it show up the same on all OS)

attempt 1:

2005年,美国加利福尼亚大学河滨分校的心理学家索尼娅•柳�┟锥�斯基(Sonja Lyubomirsky)和她的同事一起,对多项研究的结论进行了综述,这些研究显示幸福与成功呈正比。

attempt 2:

2005年,美国加利福尼亚大学河滨分校的心理学家索尼娅•柳博米尔斯基(Sonja Lyubomirsky)和她的同事一起,对多项研究的结论进行了综述,这些研究显示幸福与成功呈正比。

There are also instance where both attempts result in the same garbled text.

attempt 1:

那些能够产生“心流”的任务不能太单调沉闷,�荒苋萌瞬�生挫败感;它还要具有充分的挑战性,要求一个人全神贯注。

attempt 2:

那些能够产生“心流”的任务不能太单调沉闷,�荒苋萌瞬�生挫败感;它还要具有充分的挑战性,要求一个人全神贯注。

and we use needle similar to this:

    stream.on('data', function(chunk) {
        if (chunk === null) {
            return;
        }

        raw.push(chunk);
    });

    stream.on('end', function(err) {
        body = Buffer.concat(raw).toString();
    });

Default charset set to UTF-8 and charset is not always the second param of header

ISO-8859 was the default of HTML4 but now it's UTF-8 for HTML5. I think it's a better idea now to set default charset to UTF-8. Besides, when detecting charset in parse_content_type(), charset is not always the second param. So it's a better to match it agains the whole header.

  parse_content_type: function(header) {
    if (!header || header == '') return {};

    var charset = 'utf-8';
    var arr = header.split(';');
    try { charset = header.match(/charset=([^;]+)/)[1] } catch (e) { /* not found */ }

    return { type: arr[0], charset: charset };
  },

Encoding not recognized: 'UTF-8' (searched as: 'utf8')

I am trying to work via proxy, but got this exception.
How to solve it?

Error: Encoding not recognized: '"UTF-8"' (searched as: '"utf8"')
at Object.module.exports.getCodec (/home/nodeuser/node_modules/needle/node_modules/iconv-lite/index.js:36:23)
at Object.module.exports.fromEncoding (/home/nodeuser/node_modules/needle/node_modules/iconv-lite/index.js:7:22)
at Object.Needle.response_end (/home/nodeuser/node_modules/needle/lib/needle.js:240:38)
at IncomingMessage. (/home/nodeuser/node_modules/needle/lib/needle.js:187:16)
at IncomingMessage.EventEmitter.emit (events.js:117:20)
at _stream_readable.js:870:14
at process._tickCallback (node.js:415:13)

Host header port incorrect

Without reverse proxy it works fine.

If you specify a reverse proxy with a port number not matching the port in the get url the port number in Host header is not correct in a GET request.

Example:
url request is http://testserver.testdomain.com/image.jpg
proxy = http://proxyserver.testdomain.com:8080

GET http://testserver.testdomain.com/image.jpg
Header:
Host: http://testserver.testdomain.com:8080

Needle.js
178 remote = proxy ? url.parse(proxy) : url.parse(uri);

182 opts.port = remote.port || (remote.protocol == 'https:' ? 443 : 80);

187 if (!opts.headers['Host']) {
188 opts.headers['Host'] = proxy ? url.parse(uri).hostname : remote.hostname;
189 if (opts.port != 80 && opts.port != 443)
190 opts.headers['Host'] += ':' + opts.port;
191 }

Compressed body isn't decompressed in stream

Correct me if I'm wrong, but it appears as if the resp.body is only unzipped if you use the callback approach and not when you're using the streaming approach:

This is where the data is pushed on the stream: https://github.com/tomas/needle/blob/master/lib/needle.js#L252

And this is where the compressed data is decoded:
https://github.com/tomas/needle/blob/master/lib/needle.js#L274

Is this conclusion correct? Is there a reason that this approach is chosen (a buffering-approach instead of a streams-approach)?

Anyway, I would like to make an attempt at patching this tomorrow.

What I was thinking about:

  • get rid the the 'chunks' array in send_request;
  • make the config.out stream the "primary" way for processing the body
  • if the client has requested a callback, "sniff" all data going over the stream and store it in resp.body

Getting the stream to decompress would then be a matter of piping config.out through zlib.

2-argument callback instead of 3-argument

Hi there,

Something that noticed was the callback that adds a third argument which is a shortcut to response.body. The node.js community has pretty much settled on the 2-argument callback, which in turn caused a whole ecosystem of utilities that make this assumption (eg node-async and Q).

What is the rationale behind this? And would you mind it if I made a patch that detects the arity of the callback and behave in a standard-compliant way again if the caller requests so?

BTW I must be overwhelming you a bit with all these requests but I think there really is a place in the node ecosystem for this library, since it is one of the few libraries that actually is pleasant to use. :)

Get callback get fired twice

I have this URL: http://panoramaproductions.electionemail.com/lt.php?nl=26&c=86&m=210&s=463b49e18dfd5a13da71ff10dc210d70&l=open

Callback get fired, first, with error from here:

 request.on('error', function(err) {
      debug('Request error', err);
      if (timer) clearTimeout(timer);

      error_stop(err || new Error('Unknown error when making request.'));
    });

and the error is { [Error: Parse Error] bytesParsed: 311, code: 'HPE_INVALID_CONSTANT' }

But it will be also fired with no error from here :

// Only aggregate the full body if a callback was requested.
      if (callback) {
        resp.body  = [];
        resp.bytes = 0;

        // Count the amount of (raw) bytes passed using a PassThrough stream.
        [...] 
          callback(null, resp, resp.body);
        });

      }

It may not seem like a real issue, but this bug makes needle problematic when using it with async, because triggers the async error:
if (called) throw new Error("Callback was already called.");

needle : 0.7.2
node : v0.10.19
OS: ubuntu x64 with kernel 3.8.0-29-generic

Cannot use proxy option to send https to the proxy

When I run needle with the proxy option, I can only get needle to work when setting the proxy to http and not https.

Sample options are shown below.
rpurl = "https://endpointurl.com";

options = {
auth: 'auto',
proxy: 'http://sampleproxy.com:8080',
multipart: true
};

In this example, needle sends http protocol to the proxy even though the endpoint url is requiring https. So the proxy gets http and is required to translate to https. This is causing us some problems in negotiating cipher suites. We have a work around right now but would prefer to be able to run end to end https from needle to the proxy to the endpoint url.

When I try "proxy: 'https://sampleproxy.com:8080'", I receive the following error.

Error:
[Error: 140735290954512:error:140770FC:SSL routines:SSL23_GET_SERVER_HELLO:unknown protocol:../deps/openssl/openssl/ssl/s23_clnt.c:766:

Note that when running curl behind a proxy, curl initiates the https session with the proxy which simply passes the session out to the endpoint url. Curl appears to use the /etc/environment file proxy settings.

needle.post() calling callback twice

I'm using needle.post() and passing in a callback function. The callback function is getting called twice and the body on the first time is a javascript object (as expected) and the second time the body is a JSON string. Using the latest version of needle.

Regression in receiving large payloads

Here's a block of code using the library pgte/nock (I think the version is irrelevant) and needle:

var needle = require("needle");
var nock = require("nock");

nock("http://www.abc.com").get("/abc").reply(200, (new Array(1024 * 1024 + 1)).join("."));
needle.get("http://www.abc.com/abc", function(e, r, b) { console.log("done, %d", b.length); });

The request gets intercepted by nock and a 1MB response is sent. In needle 0.6.6 this code behaves as expected, printing done, 1048576. In needle 0.7.0 and up, the response never arrives (and node doesn't know to wait, apparently, so the program exits without printing anything).

This could be related to this issue in nock, but it's nevertheless a clear regression in needle.

About digest authorization!

there are maybe a bug, when I use needle digest authorization.

it does't work for RFC 2069, only supports RFC 2617.

auth.js line 46 should be

if (typeof challenge.qop === 'string') {
    cnonce = md5(Math.random().toString(36)).substr(0, 8);
    nc = digest.update_nc(nc);
    resp = resp.concat(nc, cnonce);
    resp = resp.concat(challenge.qop, ha2);
} else {
    resp = resp.concat(ha2);
}

and

var ha1 = md5(user + ':' + challenge.realm + ':' + pass), ha2 = md5(method
        + ':' + path), resp = [ ha1, challenge.nonce ];

and can't be the parameters of function.

they should be get from the headers of response.

needle.delete fails when data=null

when setting data=null on a needle.delete - nginx returns 411 error content-length not set. All works fine if you set data=" " - notice there a space inside the quotes.

Buffer

When I send a POST request I'm getting back a Buffer in the request body. How do I handle this?

{ ...
resume: [Function],
read: [Function],
bytes: 0,
body: < Buffer > }

Header host missing port

Just been looking at a request made from needle 0.5.9. It looks like the header's host value is missing the port, any chance this could be added?

Looks like the offending line may be lib\needle.js 172

opts.headers['Host'] = proxy ? url.parse(uri).hostname : remote.hostname;

Edit: Adding a thanks in advance/just for looking :)

Requires Node 0.10.x but package.json implies 0.4.x

Needle seems to require stream.Transform which is new to Node v0.10.

Using Node 0.8.26:

util.js:538
  ctor.prototype = Object.create(superCtor.prototype, {
                                          ^
TypeError: Cannot read property 'prototype' of undefined
    at exports.inherits (util.js:538:43)
    at Object.<anonymous> (/path/node_modules/needle/lib/decoder.js:5:1)
    at Module._compile (module.js:449:26)
    at Object.Module._extensions..js (module.js:467:10)
    at Module.load (module.js:356:32)
    at Function.Module._load (module.js:312:12)
    at Module.require (module.js:362:17)
    at require (module.js:378:17)
    at Object.<anonymous> (/path/node_modules/needle/lib/needle.js:17:19)
    at Module._compile (module.js:449:26)

timeout not work

I use
needle.get('some url'
,{"timeout":20000,"headers" : {"Depth" : "1", "Content-Type": 'text/xml; charset="utf-8"'}}
,function(err,res){
....do something....
}).pipe(out);

when downloading large file and network Interrupt,callback is not fired.and timeout is not working too.

Should use streams, not block

Use of readFileSync is a very bad idea, as it will block the whole process.
Instead, the client should accept a Stream (and internally can createReadStream from any passed file paths).
Ideally this stream would be piped straight into the request object, but even buffering it internally would be preferably to the the current blocking behaviour.

Digest Auth Fails

Not 100% sure, but when I was using this against the moodstock API, the digest auth was failing. I am pretty sure it was because the first authorisation response is setting nc=2 instead of 1. I simply set the nc to be 0 in the digest.generate function and it all worked fine for me.

Detect encoding from HTML/XML <meta> header

Some server does not notify charset in HTTP header, it's better to also sniff from the HTML/XML <meta> header.

I've done so using charset.

var encoding = 'utf8';  // defaults to 'utf8'
if (opts.encoding) {
  encoding = opts.encoding;
  console.info("Using encoding from options: " + encoding);
}
else {
  // detect charset from header or body (the first 1Kb)
  var cs = charset(res.headers, body.toString('utf8', 0, 1024));
  if (cs) {
    encoding = cs;
    console.info("Detected encoding: " + encoding);
  }
}
var str = iconv.decode(body, encoding);

Incompatible with node 0.4.x

Love the simplicity of needle, but it looks like needle doesn't really support 0.4.x. Since I'm hosting on heroku, I'm stuck with node 0.4.7.

The main issues are:

  • commit 36c797f1 requires a json file. node 0.4.12 can only import js files as modules. JSON import came with node 0.6.x. Not sure how to fix this short of just harcoding the version number and bumping it with each release.
  • commit d12105dd depends on the zlib module, which was also added in node 0.6.0. node-zlib exists and conflicts with the new 0.6.x module, but a better option might try/catching zlib and just saying zlib is only supported for 0.6.x.
  • commit 8642c3de doesn't quite fix paths for older node, since it drops the query string. As shown in the 0.6.x docs, path = pathname + search

I'll fork and submit a pull request for the latter two, but I'm not sure about the best course of action to fix the first issue.

Is there any way to track the progress?

I used needle to upload my file into my server.
There is any way to track upload progress ?

 var data = {
    fileUpload: { file: file, content_type: 'application/octet-stream' }
}; 
needle.post(config.URL+api, data, { multipart: true}, function(err, resp, body) {
...
});

Post Request with basic authentication fails

I am using needle API to make POST request to REST API and it always returns 401 error , where as if i use the same credentials to make GET request it is successful. I am not sure what am i doing wrong. Or is it some problem with Needle API. Below is my code

var needle = require('needle');
var async = require('async');

var credentials = {
username: "myusername",
password:"mypassword"

}

var data = {
name:"Test Repo",

}

needle.post('https://bitbucket.org/api/1.0/repositories/accountName/repoName/issues/10/comments',credentials,data,function(err,resp,body){
console.log(resp.statusCode) //always returns 401 error.
});

needle.get('https://bitbucket.org/api/1.0/repositories/accountName/repoName/issues/10/comments',credentials,function(err,resp,body){
console.log(resp.statusCode) //always returns 200 error.
});

Filenames not encoded

For example, one can add a filename containing a " or Unicode characters, resulting in an invalid request.

Overriding supplied Content-Type header

I specify the content type header in the options object as shown below.

var options = {
    compressed : true,
    headers : {
        'Content-Type' : 'application/soap+xml"'
    }
}

I then make a POST request with a string as the data argument. Needle overrides my content type with application/x-www-form-urlencoded.

I can get around this with the following hack at line 114 of needle.js

if ( config.headers['Content-Type'] === undefined ) {
   config.headers['Content-Type'] = content_type;
}

More than 10 requests at the same time fail!

Hello. I am trying to make 10 simultaneous POST requests to get some data from ElasticSearch

arrayWithFiveElements.forEach(function (country) {
    needle.post('http://localhost:9200/' + country + '/test_document1/_search?size=1000',
        '{"query": {"match_all": {}}}', function (err, response, body) {
            populateList(err, testData, body);
        });

    needle.post('http://localhost:9200/' + country + '/test_document2/_search?size=1000',
        '{"query": {"match_all": {}}}', function (err, response, body) {
            populateList(err, testData, body);
        });
});

and on the 9th and 10th request I always get this error:

Trace: [ERROR] No hits returned by Elastic Search for D1
    at populateMonthList (/opt/ddd/backend/backend.js:48:21)
    at /opt/ddd/backend/backend.js:74:17
    at error_stop (/opt/ddd/backend/node_modules/needle/lib/needle.js:206:9)
    at ClientRequest.<anonymous> (/opt/delivery-dashboard/backend/node_modules/needle/lib/needle.js:342:7)
    at ClientRequest.EventEmitter.emit (events.js:95:17)
    at Socket.socketErrorListener (http.js:1547:9)
    at Socket.EventEmitter.emit (events.js:95:17)
    at onwriteError (_stream_writable.js:239:10)
    at onwrite (_stream_writable.js:257:5)
    at WritableState.onwrite (_stream_writable.js:97:5)
{
  "code": "EPIPE",
  "errno": "EPIPE",
  "syscall": "write"
}
Trace: [ERROR] No hits returned by Elastic Search for D2
    at populateRWAMonthList (/opt/ddd/backend/backend.js:59:21)
    at error_stop (/opt/ddd/backend/node_modules/needle/lib/needle.js:206:9)
    at ClientRequest.<anonymous> (/opt/ddd/backend/node_modules/needle/lib/needle.js:342:7)
    at ClientRequest.EventEmitter.emit (events.js:95:17)
    at Socket.socketErrorListener (http.js:1547:9)
    at Socket.EventEmitter.emit (events.js:95:17)
    at onwriteError (_stream_writable.js:239:10)
    at onwrite (_stream_writable.js:257:5)
    at WritableState.onwrite (_stream_writable.js:97:5)
    at fireErrorCallbacks (net.js:437:13)
{
  "code": "EPIPE",
  "errno": "EPIPE",
  "syscall": "write"
}

The other 8 requests are totally fine.

Do you happen to know the solution to this issue?

Saving binary file

Hello,

i am trying to download and save binary file (image).

needle.get(url, { output: path }, function (err, resp, body) {

});

But I have got the error:

{ [Error: ENOENT, open '$PATH_TO_FILE$FILENAME']
errno: 34,
code: 'ENOENT',
path: '$PATH_TO_FILE$FILENAME' }

Could you suggest why I am getting this?

p.s. i have existing directories on the fie path.
By the way, is there possibility to enable creating missed directories
recursively?

missing cookie header on redirect

Hello...
I am Heepan Kim.

First of all, thank you for sharing your code.

Original source code line 208

// if redirect code is found, send a GET request to that location if enabled via 'follow' option
if ([301, 302].indexOf(resp.statusCode) != -1 && headers.location) {
  if (count <= config.follow)
    return self.send_request(++count, 'GET', url.resolve(uri, headers.location), config, null, callback);
  else if (config.follow > 0)
    return callback(new Error('Max redirects reached. Possible loop in: ' + headers.location));
}

This code not working on cookie based server.

So I suggest my code.

// if redirect code is found, send a GET request to that location if enabled via 'follow' option
if ([301, 302].indexOf(resp.statusCode) != -1 && headers.location) {
  if (count <= config.follow) {
    // --------------------------------------------------
    // must set cookie
    var cookies = headers['set-cookie'];
    if ( cookies ) {
      config.base_opts.headers.Cookie = cookies[0];
    }
    // --------------------------------------------------
    return self.send_request(++count, 'GET', url.resolve(uri, headers.location), config, null, callback);
  }
  else if (config.follow > 0)
    return callback(new Error('Max redirects reached. Possible loop in: ' + headers.location));
}

Integers in JSON are converted to strings

I sent a POST request using needle like this:

needle.post(SERVICE_SOCKET + '/verify',
          {
            message: {
              value1: 1.2456,
              value2: -100,
              value3: 100,
            }
          },
          {},
          function (err, resp) {
            done(err);
          });

The receiver then got an object like this:

{
  value1: '1.2456',
  value2: '-100',
  value3: '100'
}

But I was expecting the following (as I get it using restify):

{
  value1: 1.2456,
  value2: -100,
  value3: 100
}

As we are using both needle and restify clients I have to handle both data formats which is quite dissatisfying. Am I missing an option or is this a bug?

Limit download length (size) but still make use of decoder and parser

I have been using needle lately and come into a dilemma: it is possible to both cap download length and avoid having to deal with stream chunk myself?

From source code it seems that passing a callback will cause buffering but we have no way of knowing how much data has been buffered so far (thus not able to stop it). On the other hand if we handle readable stream ourselves I believe parser or decoder will be kick in (they need full buffer anyway).

Any suggestion?

PS: many thx for this package, i came from a curl background so understandably some features are not here for granted.

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.