├── .gitignore ├── .npmignore ├── AUTHORS ├── LICENSE ├── README.md ├── index.js ├── package.json └── test.js /.gitignore: -------------------------------------------------------------------------------- 1 | .*.swp 2 | /dist 3 | node_modules 4 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | /.gitmodules 2 | /Rakefile 3 | /template 4 | -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | Jason Smith Work 2 | Jason Smith 3 | maxogden 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Browser Request: The easiest HTTP library you'll ever see 2 | 3 | Browser Request is a port of Mikeal Rogers's ubiquitous and excellent [request][req] package to the browser. 4 | 5 | Jealous of Node.js? Pining for clever callbacks? Request is for you. 6 | 7 | Don't care about Node.js? Looking for less tedium and a no-nonsense API? Request is for you too. 8 | 9 | [![browser support](https://ci.testling.com/iriscouch/browser-request.png)](https://ci.testling.com/maxogden/browser-request) 10 | 11 | # Examples 12 | 13 | Fetch a resource: 14 | 15 | ```javascript 16 | request('/some/resource.txt', function(er, response, body) { 17 | if(er) 18 | throw er; 19 | console.log("I got: " + body); 20 | }) 21 | ``` 22 | 23 | Send a resource: 24 | 25 | ```javascript 26 | request.put({uri:'/some/resource.xml', body:''}, function(er, response) { 27 | if(er) 28 | throw new Error("XML PUT failed (" + er + "): HTTP status was " + response.status); 29 | console.log("Stored the XML"); 30 | }) 31 | ``` 32 | 33 | To work with JSON, set `options.json` to `true`. Request will set the `Content-Type` and `Accept` headers, and handle parsing and serialization. 34 | 35 | ```javascript 36 | request({method:'POST', url:'/db', body:'{"relaxed":true}', json:true}, on_response) 37 | 38 | function on_response(er, response, body) { 39 | if(er) 40 | throw er 41 | if(result.ok) 42 | console.log('Server ok, id = ' + result.id) 43 | } 44 | ``` 45 | 46 | Or, use this shorthand version (pass data into the `json` option directly): 47 | 48 | ```javascript 49 | request({method:'POST', url:'/db', json:{relaxed:true}}, on_response) 50 | ``` 51 | 52 | ## Convenient CouchDB 53 | 54 | Browser Request provides a CouchDB wrapper. It is the same as the JSON wrapper, however it will indicate an error if the HTTP query was fine, but there was a problem at the database level. The most common example is `409 Conflict`. 55 | 56 | ```javascript 57 | request.couch({method:'PUT', url:'/db/existing_doc', body:{"will_conflict":"you bet!"}}, function(er, resp, result) { 58 | if(er.error === 'conflict') 59 | return console.error("Couch said no: " + er.reason); // Output: Couch said no: Document update conflict. 60 | 61 | if(er) 62 | throw er; 63 | 64 | console.log("Existing doc stored. This must have been the first run."); 65 | }) 66 | ``` 67 | 68 | See the [Node.js Request README][req] for several more examples. Request intends to maintain feature parity with Node request (except what the browser disallows). If you find a discrepancy, please submit a bug report. Thanks! 69 | 70 | # Usage 71 | 72 | ## Browserify 73 | 74 | Browser Request is a [browserify][browserify]-enabled package. 75 | 76 | First, add `browser-request` to your Node project 77 | 78 | $ npm install browser-request 79 | 80 | Next, make a module that uses the package. 81 | 82 | ```javascript 83 | // example.js - Example front-end (client-side) code using browser-request via browserify 84 | // 85 | var request = require('browser-request') 86 | request('/', function(er, res) { 87 | if(!er) 88 | return console.log('browser-request got your root path:\n' + res.body) 89 | 90 | console.log('There was an error, but at least browser-request loaded and ran!') 91 | throw er 92 | }) 93 | ``` 94 | 95 | To build this for the browser, run it through browserify. 96 | 97 | $ browserify --entry example.js --outfile example-built.js 98 | 99 | Deploy `example-built.js` to your web site and use it from your page. 100 | 101 | ```html 102 | 103 | ``` 104 | ## License 105 | 106 | Browser Request is licensed under the Apache 2.0 license. 107 | 108 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | // Browser Request 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | var XHR = XMLHttpRequest 16 | if (!XHR) throw new Error('missing XMLHttpRequest') 17 | request.log = { 18 | 'trace': noop, 'debug': noop, 'info': noop, 'warn': noop, 'error': noop 19 | } 20 | 21 | var DEFAULT_TIMEOUT = 3 * 60 * 1000 // 3 minutes 22 | 23 | // 24 | // request 25 | // 26 | 27 | function request(options, callback) { 28 | // The entry-point to the API: prep the options object and pass the real work to run_xhr. 29 | if(typeof callback !== 'function') 30 | throw new Error('Bad callback given: ' + callback) 31 | 32 | if(!options) 33 | throw new Error('No options given') 34 | 35 | var options_onResponse = options.onResponse; // Save this for later. 36 | 37 | if(typeof options === 'string') 38 | options = {'uri':options}; 39 | else 40 | options = JSON.parse(JSON.stringify(options)); // Use a duplicate for mutating. 41 | 42 | options.onResponse = options_onResponse // And put it back. 43 | 44 | if (options.verbose) request.log = getLogger(); 45 | 46 | if(options.url) { 47 | options.uri = options.url; 48 | delete options.url; 49 | } 50 | 51 | if(!options.uri && options.uri !== "") 52 | throw new Error("options.uri is a required argument"); 53 | 54 | if(typeof options.uri != "string") 55 | throw new Error("options.uri must be a string"); 56 | 57 | var unsupported_options = ['proxy', '_redirectsFollowed', 'maxRedirects', 'followRedirect'] 58 | for (var i = 0; i < unsupported_options.length; i++) 59 | if(options[ unsupported_options[i] ]) 60 | throw new Error("options." + unsupported_options[i] + " is not supported") 61 | 62 | options.callback = callback 63 | options.method = options.method || 'GET'; 64 | options.headers = options.headers || {}; 65 | options.body = options.body || null 66 | options.timeout = options.timeout || request.DEFAULT_TIMEOUT 67 | 68 | if(options.headers.host) 69 | throw new Error("Options.headers.host is not supported"); 70 | 71 | if(options.json) { 72 | options.headers.accept = options.headers.accept || 'application/json' 73 | if(options.method !== 'GET') 74 | options.headers['content-type'] = 'application/json' 75 | 76 | if(typeof options.json !== 'boolean') 77 | options.body = JSON.stringify(options.json) 78 | else if(typeof options.body !== 'string') 79 | options.body = JSON.stringify(options.body) 80 | } 81 | 82 | //BEGIN QS Hack 83 | var serialize = function(obj) { 84 | var str = []; 85 | for(var p in obj) 86 | if (obj.hasOwnProperty(p)) { 87 | str.push(encodeURIComponent(p) + "=" + encodeURIComponent(obj[p])); 88 | } 89 | return str.join("&"); 90 | } 91 | 92 | if(options.qs){ 93 | var qs = (typeof options.qs == 'string')? options.qs : serialize(options.qs); 94 | if(options.uri.indexOf('?') !== -1){ //no get params 95 | options.uri = options.uri+'&'+qs; 96 | }else{ //existing get params 97 | options.uri = options.uri+'?'+qs; 98 | } 99 | } 100 | //END QS Hack 101 | 102 | //BEGIN FORM Hack 103 | var multipart = function(obj) { 104 | //todo: support file type (useful?) 105 | var result = {}; 106 | result.boundry = '-------------------------------'+Math.floor(Math.random()*1000000000); 107 | var lines = []; 108 | for(var p in obj){ 109 | if (obj.hasOwnProperty(p)) { 110 | lines.push( 111 | '--'+result.boundry+"\n"+ 112 | 'Content-Disposition: form-data; name="'+p+'"'+"\n"+ 113 | "\n"+ 114 | obj[p]+"\n" 115 | ); 116 | } 117 | } 118 | lines.push( '--'+result.boundry+'--' ); 119 | result.body = lines.join(''); 120 | result.length = result.body.length; 121 | result.type = 'multipart/form-data; boundary='+result.boundry; 122 | return result; 123 | } 124 | 125 | if(options.form){ 126 | if(typeof options.form == 'string') throw('form name unsupported'); 127 | if(options.method === 'POST'){ 128 | var encoding = (options.encoding || 'application/x-www-form-urlencoded').toLowerCase(); 129 | options.headers['content-type'] = encoding; 130 | switch(encoding){ 131 | case 'application/x-www-form-urlencoded': 132 | options.body = serialize(options.form).replace(/%20/g, "+"); 133 | break; 134 | case 'multipart/form-data': 135 | var multi = multipart(options.form); 136 | //options.headers['content-length'] = multi.length; 137 | options.body = multi.body; 138 | options.headers['content-type'] = multi.type; 139 | break; 140 | default : throw new Error('unsupported encoding:'+encoding); 141 | } 142 | } 143 | } 144 | //END FORM Hack 145 | 146 | // If onResponse is boolean true, call back immediately when the response is known, 147 | // not when the full request is complete. 148 | options.onResponse = options.onResponse || noop 149 | if(options.onResponse === true) { 150 | options.onResponse = callback 151 | options.callback = noop 152 | } 153 | 154 | // XXX Browsers do not like this. 155 | //if(options.body) 156 | // options.headers['content-length'] = options.body.length; 157 | 158 | // HTTP basic authentication 159 | if(!options.headers.authorization && options.auth) 160 | options.headers.authorization = 'Basic ' + b64_enc(options.auth.username + ':' + options.auth.password); 161 | 162 | return run_xhr(options) 163 | } 164 | 165 | var req_seq = 0 166 | function run_xhr(options) { 167 | var xhr = new XHR 168 | , timed_out = false 169 | , is_cors = is_crossDomain(options.uri) 170 | , supports_cors = ('withCredentials' in xhr) 171 | 172 | req_seq += 1 173 | xhr.seq_id = req_seq 174 | xhr.id = req_seq + ': ' + options.method + ' ' + options.uri 175 | xhr._id = xhr.id // I know I will type "_id" from habit all the time. 176 | 177 | if(is_cors && !supports_cors) { 178 | var cors_err = new Error('Browser does not support cross-origin request: ' + options.uri) 179 | cors_err.cors = 'unsupported' 180 | return options.callback(cors_err, xhr) 181 | } 182 | 183 | xhr.timeoutTimer = setTimeout(too_late, options.timeout) 184 | function too_late() { 185 | timed_out = true 186 | var er = new Error('ETIMEDOUT') 187 | er.code = 'ETIMEDOUT' 188 | er.duration = options.timeout 189 | 190 | request.log.error('Timeout', { 'id':xhr._id, 'milliseconds':options.timeout }) 191 | return options.callback(er, xhr) 192 | } 193 | 194 | // Some states can be skipped over, so remember what is still incomplete. 195 | var did = {'response':false, 'loading':false, 'end':false} 196 | 197 | xhr.onreadystatechange = on_state_change 198 | xhr.open(options.method, options.uri, true) // asynchronous 199 | if(is_cors) 200 | xhr.withCredentials = !! options.withCredentials 201 | xhr.send(options.body) 202 | return xhr 203 | 204 | function on_state_change(event) { 205 | if(timed_out) 206 | return request.log.debug('Ignoring timed out state change', {'state':xhr.readyState, 'id':xhr.id}) 207 | 208 | request.log.debug('State change', {'state':xhr.readyState, 'id':xhr.id, 'timed_out':timed_out}) 209 | 210 | if(xhr.readyState === XHR.OPENED) { 211 | request.log.debug('Request started', {'id':xhr.id}) 212 | for (var key in options.headers) 213 | xhr.setRequestHeader(key, options.headers[key]) 214 | } 215 | 216 | else if(xhr.readyState === XHR.HEADERS_RECEIVED) 217 | on_response() 218 | 219 | else if(xhr.readyState === XHR.LOADING) { 220 | on_response() 221 | on_loading() 222 | } 223 | 224 | else if(xhr.readyState === XHR.DONE) { 225 | on_response() 226 | on_loading() 227 | on_end() 228 | } 229 | } 230 | 231 | function on_response() { 232 | if(did.response) 233 | return 234 | 235 | did.response = true 236 | request.log.debug('Got response', {'id':xhr.id, 'status':xhr.status}) 237 | clearTimeout(xhr.timeoutTimer) 238 | xhr.statusCode = xhr.status // Node request compatibility 239 | 240 | // Detect failed CORS requests. 241 | if(is_cors && xhr.statusCode == 0) { 242 | var cors_err = new Error('CORS request rejected: ' + options.uri) 243 | cors_err.cors = 'rejected' 244 | 245 | // Do not process this request further. 246 | did.loading = true 247 | did.end = true 248 | 249 | return options.callback(cors_err, xhr) 250 | } 251 | 252 | options.onResponse(null, xhr) 253 | } 254 | 255 | function on_loading() { 256 | if(did.loading) 257 | return 258 | 259 | did.loading = true 260 | request.log.debug('Response body loading', {'id':xhr.id}) 261 | // TODO: Maybe simulate "data" events by watching xhr.responseText 262 | } 263 | 264 | function on_end() { 265 | if(did.end) 266 | return 267 | 268 | did.end = true 269 | request.log.debug('Request done', {'id':xhr.id}) 270 | 271 | xhr.body = xhr.responseText 272 | if(options.json) { 273 | try { xhr.body = JSON.parse(xhr.responseText) } 274 | catch (er) { return options.callback(er, xhr) } 275 | } 276 | 277 | options.callback(null, xhr, xhr.body) 278 | } 279 | 280 | } // request 281 | 282 | request.withCredentials = false; 283 | request.DEFAULT_TIMEOUT = DEFAULT_TIMEOUT; 284 | 285 | // 286 | // defaults 287 | // 288 | 289 | request.defaults = function(options, requester) { 290 | var def = function (method) { 291 | var d = function (params, callback) { 292 | if(typeof params === 'string') 293 | params = {'uri': params}; 294 | else { 295 | params = JSON.parse(JSON.stringify(params)); 296 | } 297 | for (var i in options) { 298 | if (params[i] === undefined) params[i] = options[i] 299 | } 300 | return method(params, callback) 301 | } 302 | return d 303 | } 304 | var de = def(request) 305 | de.get = def(request.get) 306 | de.post = def(request.post) 307 | de.put = def(request.put) 308 | de.head = def(request.head) 309 | return de 310 | } 311 | 312 | // 313 | // HTTP method shortcuts 314 | // 315 | 316 | var shortcuts = [ 'get', 'put', 'post', 'head' ]; 317 | shortcuts.forEach(function(shortcut) { 318 | var method = shortcut.toUpperCase(); 319 | var func = shortcut.toLowerCase(); 320 | 321 | request[func] = function(opts) { 322 | if(typeof opts === 'string') 323 | opts = {'method':method, 'uri':opts}; 324 | else { 325 | opts = JSON.parse(JSON.stringify(opts)); 326 | opts.method = method; 327 | } 328 | 329 | var args = [opts].concat(Array.prototype.slice.apply(arguments, [1])); 330 | return request.apply(this, args); 331 | } 332 | }) 333 | 334 | // 335 | // CouchDB shortcut 336 | // 337 | 338 | request.couch = function(options, callback) { 339 | if(typeof options === 'string') 340 | options = {'uri':options} 341 | 342 | // Just use the request API to do JSON. 343 | options.json = true 344 | if(options.body) 345 | options.json = options.body 346 | delete options.body 347 | 348 | callback = callback || noop 349 | 350 | var xhr = request(options, couch_handler) 351 | return xhr 352 | 353 | function couch_handler(er, resp, body) { 354 | if(er) 355 | return callback(er, resp, body) 356 | 357 | if((resp.statusCode < 200 || resp.statusCode > 299) && body.error) { 358 | // The body is a Couch JSON object indicating the error. 359 | er = new Error('CouchDB error: ' + (body.error.reason || body.error.error)) 360 | for (var key in body) 361 | er[key] = body[key] 362 | return callback(er, resp, body); 363 | } 364 | 365 | return callback(er, resp, body); 366 | } 367 | } 368 | 369 | // 370 | // Utility 371 | // 372 | 373 | function noop() {} 374 | 375 | function getLogger() { 376 | var logger = {} 377 | , levels = ['trace', 'debug', 'info', 'warn', 'error'] 378 | , level, i 379 | 380 | for(i = 0; i < levels.length; i++) { 381 | level = levels[i] 382 | 383 | logger[level] = noop 384 | if(typeof console !== 'undefined' && console && console[level]) 385 | logger[level] = formatted(console, level) 386 | } 387 | 388 | return logger 389 | } 390 | 391 | function formatted(obj, method) { 392 | return formatted_logger 393 | 394 | function formatted_logger(str, context) { 395 | if(typeof context === 'object') 396 | str += ' ' + JSON.stringify(context) 397 | 398 | return obj[method].call(obj, str) 399 | } 400 | } 401 | 402 | // Return whether a URL is a cross-domain request. 403 | function is_crossDomain(url) { 404 | var rurl = /^([\w\+\.\-]+:)(?:\/\/([^\/?#:]*)(?::(\d+))?)?/ 405 | 406 | // jQuery #8138, IE may throw an exception when accessing 407 | // a field from window.location if document.domain has been set 408 | var ajaxLocation 409 | try { ajaxLocation = location.href } 410 | catch (e) { 411 | // Use the href attribute of an A element since IE will modify it given document.location 412 | ajaxLocation = document.createElement( "a" ); 413 | ajaxLocation.href = ""; 414 | ajaxLocation = ajaxLocation.href; 415 | } 416 | 417 | var ajaxLocParts = rurl.exec(ajaxLocation.toLowerCase()) || [] 418 | , parts = rurl.exec(url.toLowerCase() ) 419 | 420 | var result = !!( 421 | parts && 422 | ( parts[1] != ajaxLocParts[1] 423 | || parts[2] != ajaxLocParts[2] 424 | || (parts[3] || (parts[1] === "http:" ? 80 : 443)) != (ajaxLocParts[3] || (ajaxLocParts[1] === "http:" ? 80 : 443)) 425 | ) 426 | ) 427 | 428 | //console.debug('is_crossDomain('+url+') -> ' + result) 429 | return result 430 | } 431 | 432 | // MIT License from http://phpjs.org/functions/base64_encode:358 433 | function b64_enc (data) { 434 | // Encodes string using MIME base64 algorithm 435 | var b64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="; 436 | var o1, o2, o3, h1, h2, h3, h4, bits, i = 0, ac = 0, enc="", tmp_arr = []; 437 | 438 | if (!data) { 439 | return data; 440 | } 441 | 442 | // assume utf8 data 443 | // data = this.utf8_encode(data+''); 444 | 445 | do { // pack three octets into four hexets 446 | o1 = data.charCodeAt(i++); 447 | o2 = data.charCodeAt(i++); 448 | o3 = data.charCodeAt(i++); 449 | 450 | bits = o1<<16 | o2<<8 | o3; 451 | 452 | h1 = bits>>18 & 0x3f; 453 | h2 = bits>>12 & 0x3f; 454 | h3 = bits>>6 & 0x3f; 455 | h4 = bits & 0x3f; 456 | 457 | // use hexets to index into b64, and append result to encoded string 458 | tmp_arr[ac++] = b64.charAt(h1) + b64.charAt(h2) + b64.charAt(h3) + b64.charAt(h4); 459 | } while (i < data.length); 460 | 461 | enc = tmp_arr.join(''); 462 | 463 | switch (data.length % 3) { 464 | case 1: 465 | enc = enc.slice(0, -2) + '=='; 466 | break; 467 | case 2: 468 | enc = enc.slice(0, -1) + '='; 469 | break; 470 | } 471 | 472 | return enc; 473 | } 474 | module.exports = request; 475 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "browser-request", 3 | "version": "0.3.2", 4 | "author": { 5 | "name": "Jason Smith", 6 | "email": "jhs@iriscouch.com" 7 | }, 8 | "description": "Browser port of the Node.js 'request' package", 9 | "keywords": [ 10 | "request", 11 | "http", 12 | "browser", 13 | "browserify" 14 | ], 15 | "homepage": "http://github.com/iriscouch/browser-request", 16 | "repository": { 17 | "type": "git", 18 | "url": "git://github.com/iriscouch/browser-request" 19 | }, 20 | "scripts": { 21 | "test": "beefy test.js" 22 | }, 23 | "devDependencies": { 24 | "tape": "~1.0.4", 25 | "beefy": "~0.4.0", 26 | "browserify": "~2.25.0" 27 | }, 28 | "engines": [ 29 | "node" 30 | ], 31 | "testling": { 32 | "files": "test.js", 33 | "browsers": [ 34 | "ie/6..latest", 35 | "firefox/3..5", 36 | "firefox/19..nightly", 37 | "chrome/4..7", 38 | "chrome/24..canary", 39 | "opera/10..next", 40 | "safari/4..latest", 41 | "iphone/6", 42 | "ipad/6" 43 | ] 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /test.js: -------------------------------------------------------------------------------- 1 | var test = require('tape') 2 | 3 | var request = require('./') 4 | 5 | test('try a CORS GET', function (t) { 6 | var url = 'https://www.googleapis.com/plus/v1/activities' 7 | request(url, function(err, resp, body) { 8 | t.equal(resp.statusCode, 400) 9 | t.equal(!!resp.body.match(/Required parameter/), true) 10 | t.end() 11 | }) 12 | }) 13 | 14 | test('json true', function (t) { 15 | var url = 'https://www.googleapis.com/plus/v1/activities' 16 | request({url: url, json: true}, function(err, resp, body) { 17 | t.equal(body.error.code, 400) 18 | t.end() 19 | }) 20 | }) 21 | --------------------------------------------------------------------------------