Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Cleanup http-client module, remove HttpClient, add support for follow…

…ing redirects (on by default)
  • Loading branch information...
commit 0d12310bb60cc8a523d9eee36db95311c8838452 1 parent d6d9bde
Tom Robinson authored
View
2  engines/default/lib/http-engine.js
@@ -1,2 +0,0 @@
-require("narwhal").deprecated("use http-client-engine instead of http-engine");
-require("narwhal/util").update(exports. require("http-client-engine"));
View
159 engines/rhino/lib/http-client-engine.js
@@ -1,99 +1,80 @@
-// -- isaacs Isaac Schlueter
// -- kriskowal Kris Kowal Copyright (C) 2009-2010 MIT License
+// -- tlrobinson Tom Robinson
-var IO = require("io").IO,
- UTIL = require("narwhal/util");
+var IO = require("io").IO;
-exports.IO = function (url) {
- return new IO(
- new java.net.URL(url).openStream(),
- null
- );
-};
+exports.open = function(url, mode, options) {
+ var connection, output, input;
-exports.connect = function HttpClient_engine_connect (tx) {
- if (tx._isConnected) return;
-
- var con = java.net.HttpURLConnection(new java.net.URL(tx.url).openConnection());
- con.setRequestMethod(tx.method.toUpperCase());
-
- UTIL.forEach(tx.headers, function (h, v) {
- con.setRequestProperty(h, v);
- });
- if (!tx.headers) tx.headers = {};
- var cl = UTIL.get(tx.headers, "Content-Length") || 0;
- if (cl > 0) {
- con.setDoOutput(true);
- var os = null;
- try {
- os = con.getOutputStream();
- } catch (ex) {}
- if (os) {
- var writer = new IO(null, con.getOutputStream());
- tx.body.forEach(function (piece) {
- writer.write(piece);
- });
- writer.close();
- }
- }
-
- try {
- con.connect();
- } catch (ex) {
- // It would be nice to do something clever and special here,
- // but I'm not feeling it at the moment.
- ex.message = [
- "Could not connect to "+tx.url+". Probably a bad hostname.",
- ex.message
- ].join("\n");
- throw ex;
- }
-
- tx._isConnected = true;
- var resp = tx._response = {status:200, headers:{}, body:[]};
-
- // Call this now to trigger the fetch asynchronously.
- // This way, if you set up multiple HttpClients, and then call connect()
- // on all of them, you'll only wait as long as the slowest one, since
- // the streams will start filling up right away.
-
-
- // now pull everything out.
- var fields = con.getHeaderFields();
- var fieldKeys = fields.keySet().toArray();
- for (var i = 0, l = fieldKeys.length; i < l; i ++ ) {
- var fieldValue = fields.get(fieldKeys[i]).toArray().join('');
- var fieldName = fieldKeys[i];
- if (fieldName === null) {
- // Something like: HTTP/1.1 200 OK
- UTIL.set(resp, "status", +(/HTTP\/1\.[01] ([0-9]{3})/.exec(fieldValue)[1]));
- // fieldName = "Status";
- resp.statusText = fieldValue;
- continue;
+ function startRequest() {
+ connection = new java.net.URL(url).openConnection();
+ connection.setDoInput(true);
+ connection.setDoOutput(true);
+ connection.setRequestMethod(options.method);
+ connection.setInstanceFollowRedirects(!!options.followRedirects);
+
+ for (var name in options.headers) {
+ if (options.headers.hasOwnProperty(name)) {
+ connection.addRequestProperty(String(name), String(options.headers[name]));
+ }
}
- UTIL.set(resp.headers, fieldName, fieldValue);
+
+ connection.connect();
+
+ output = new IO(null, connection.getOutputStream());
+ input = null;
}
- // TODO: Restructure using non-blocking IO to support asynchronous interactions.
- // In that case, you could just have a callback that gets each bytestring.
- var is = null;
- try {
- var is = con.getInputStream();
- } catch (ex) {
- return resp;
+ startRequest();
+
+ var request = {
+ status : null,
+ headers : {},
+ read : function() {
+ if (!input) {
+ output.close();
+ input = new IO(connection.getInputStream(), null);
+ this.status = Number(connection.getResponseCode());
+ this.statusText = String(connection.getResponseMessage() || "");
+ for (var i = 0; ; i++) {
+ var key = connection.getHeaderFieldKey(i), value = connection.getHeaderField(i);
+ if (!key && !value) {
+ break;
+ }
+ if (key) {
+ key = String(key);
+ value = String(value);
+ this.headers[key] = value;
+ if (key.toUpperCase() === "LOCATION")
+ url = value;
+ }
+ }
+
+ // Manually follow cross-protocol redirects because Java doesn't:
+ // http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4620571
+ if (options.followRedirects && this.status >= 300 && this.status < 400) {
+ // TODO: should we change the method to GET if it was not a GET like curl does?
+ startRequest();
+ return this.read.apply(this, arguments);
+ }
+ }
+ return input.read.apply(input, arguments);
+ },
+ write : function() {
+ output.write.apply(output, arguments);
+ return this;
+ },
+ flush : function() {
+ output.flush.apply(output, arguments);
+ return this;
+ },
+ close : function() {
+ output && output.close();
+ input && input.close();
+ return this;
+ },
+ copy : IO.prototype.copy
}
-
- // TODO: Should the input stream be rewindable?
- var reader = new IO(con.getInputStream(), null);
- resp.body = {forEach : function (block) {
- var buflen = 1024;
- for (
- var bytes = reader.read(buflen);
- bytes.length > 0;
- bytes = reader.read(buflen)
- ) block(bytes);
- }};
-
- return resp;
+ return request;
};
View
12 engines/rhino/lib/http-engine.js
@@ -1,12 +0,0 @@
-
-// -- kriskowal Kris Kowal Copyright (C) 2009-2010 MIT License
-
-var IO = require('./io').IO;
-
-exports.IO = function (url) {
- return new IO(
- new java.net.URL(url).openStream(),
- null
- );
-};
-
View
79 engines/rhino/lib/http.js
@@ -1,79 +0,0 @@
-
-// -- tlrobinson Tom Robinson
-
-var FILE = require("file");
-var IO = require("io").IO;
-
-exports.open = function(url, mode, options) {
- mode = mode || "b";
- options = options || {};
-
- options.method = options.method || "GET";
- options.headers = options.headers || {};
-
- var connection = new java.net.URL(url).openConnection();
- connection.setDoInput(true);
- connection.setDoOutput(true);
- connection.setRequestMethod(options.method);
-
- for (var name in options.headers){
- connection.addRequestProperty(String(name), String(options.headers[name]));
- }
-
- connection.connect();
-
- var output = new IO(null, connection.getOutputStream());
- var input = null;
-
- var request = {
- status : null,
- headers : {},
- read : function() {
- if (!input) {
- output.close();
- input = new IO(connection.getInputStream(), null);
- this.status = Number(connection.getResponseCode());
- this.statusText = String(connection.getResponseMessage() || "");
- for (var i = 0; ; i++) {
- var key = connection.getHeaderFieldKey(i), value = connection.getHeaderField(i);
- if (! key && ! value)
- break;
- if (key)
- this.headers[String(key)] = String(value);
- }
- }
- return input.read.apply(input, arguments);
- },
- write : function() {
- output.write.apply(output, arguments);
- return this;
- },
- flush : function() {
- output.flush.apply(output, arguments);
- return this;
- },
- close : function() {
- if (output)
- output.close();
- if (input)
- input.close();
- return this;
- },
- copy : IO.prototype.copy
- }
- return request;
-};
-
-exports.read = function(url) {
- var stream = exports.open(url);
- try {
- return stream.read();
- } finally {
- stream.close();
- }
-};
-
-exports.copy = function(source, target, mode) {
- mode = mode || "b";
- return FILE.path(target).write(exports.read(source, mode), mode);
-};
View
161 lib/http-client.js
@@ -4,15 +4,22 @@
// -- kriskowal Kris Kowal Copyright (C) 2009-2010 MIT License
// -- cadorn Christoph Dorn TODO
// -- veged Sergey Berezhnoy TODO
+// -- tlrobinson Tom Robinson
-var IO = require("io").IO,
- UTIL = require("narwhal/util"),
- ENGINE = require("http-client-engine"),
- FS = require("file");
+var ENGINE = require("http-client-engine");
+var FILE = require("file");
exports.open = function (url, mode, options) {
// todo mode negotiation, particularly for binary vs text buffering
- return new ENGINE.IO(url);
+ mode = mode || "b";
+ options = options || {};
+
+ options.method = options.method || "GET";
+ options.headers = options.headers || {};
+ if (options.followRedirects === undefined)
+ options.followRedirects = true;
+
+ return ENGINE.open(url, mode, options);
};
exports.read = function (url) {
@@ -27,147 +34,5 @@ exports.read = function (url) {
// TODO resolve the source as a file URL
exports.copy = function (source, target, mode) {
mode = mode || 'b';
- return FS.path(target).write(exports.read(source, mode), mode);
-};
-
-exports.HttpClient = HttpClient;
-
-var newTransactionId = (function () {
- var id = 1;
- return function () {
- return id ++;
- };
-})();
-
-var transaction = (function () {
- var hidden = {};
- return function (id, del) {
- if (del && hidden.hasOwnProperty(id)) {
- delete hidden[id];
- return;
- }
- if (!hidden.hasOwnProperty(id)) hidden[id] = {};
- return hidden[id];
- };
-})();
-
-// {method, url, headers, body}
-// return value is {status:status, headers:{..}, body:[..]}
-function HttpClient (settings) {
- if (!(this instanceof HttpClient)) return new HttpClient(settings);
- if (!this.txId) this.create();
- if (settings) this.setOptions(settings);
-};
-
-HttpClient.prototype = {
-
- create : function HttpClient_create () {
- // clean up, set up defaults.
- transaction(this.txId, true);
- this.txId = newTransactionId();
- this.setOptions({
- "method" : "GET",
- "headers" : {},
- "body" : []
- });
- return this;
- },
-
- setOptions : function HttpClient_setOption (settings) {
- for (var key in settings) if (settings.hasOwnProperty(key)) {
- this.setOption(key, settings[key]);
- }
- return this;
- },
-
- setOption : function HttpClient_setOption (key, val) {
- var guts = transaction(this.txId);
- switch (key) {
- case "headers":
- if (typeof val !== 'object') throw new Error(
- "HttpClient: headers must be a simple object."
- );
- return this.setHeaders(val);
- case "body":
- if (typeof val.forEach !== 'function') throw new Error(
- "HttpClient: body must be iterable."
- );
- // fallthrough
- default:
- guts[key] = val;
- }
- return this;
- },
-
- setHeaders : function HttpClient_setHeaders (headers) {
- for (var h in headers) if (headers.hasOwnProperty(h)) {
- this.setHeader(h, headers[h]);
- }
- return this;
- },
-
- setHeader : function HttpClient_setHeader (key, val) {
- var guts = transaction(this.txId);
- if (!guts.hasOwnProperty("headers")) guts.headers = {};
- UTIL.set(guts.headers, key, val);
- return this;
- },
-
- write : function HttpClient_write (data) {
- var guts = transaction(this.txId);
- var len = UTIL.get(guts.headers, "Content-Length") || 0;
- len += data.length;
- UTIL.set(guts.headers, "Content-Length", len);
- guts.body.push(data);
- return this;
- },
-
- connect : function HttpClient_connect (decode) {
- var guts = transaction(this.txId);
- var resp = ENGINE.connect(guts);
- if (decode) HttpClient.decode(resp);
- transaction(this.txId, true);
- return resp;
- }
-
+ return FILE.path(target).write(exports.read(source, mode), mode);
};
-
-HttpClient.decode = function HttpClient_decode (resp, encoding) {
- encoding = encoding || UTIL.get(resp.headers, "Content-Encoding");
- if (!encoding) {
- var contentType = UTIL.get(resp.headers, "Content-Type");
- if (contentType) {
- encoding = /charset=([^;\s]+)/.exec(contentType);
- if (encoding) encoding = encoding[1];
- }
- }
- // fall back on UTF-8. It's almost always a good choice.
- if (!encoding) encoding = 'UTF-8';
- var raw = resp.body;
- resp._rawBody = raw;
- resp.body = {forEach : function (block) {
- raw.forEach(function (i) {
- block(i.decodeToString(encoding));
- });
- }};
- return resp;
-};
-
-HttpClient.undecode = function HttpClient_undecode (resp) {
- if ("_rawBody" in resp) resp.body = resp._rawBody;
- delete resp._rawBody;
- return resp;
-};
-
-HttpClient.print = function HttpClient_print (resp) {
- var out = [];
- out.push(resp.statusText);
- UTIL.forEach(resp.headers, function (h, v) {
- out.push(h+": "+v);
- });
- out.push("");
- out=[out.join("\n")];
- resp.body.forEach(function (p) { out.push(p) });
- print(out.join(""));
-};
-
Please sign in to comment.
Something went wrong with that request. Please try again.