├── .gitignore ├── LICENSE ├── README.md ├── bin └── http-console ├── lib ├── ext │ └── index.js └── http-console.js └── package.json /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | -------------------------------------------------------------------------------- /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 | Copyright (c) 2009-2010 Alexis Sellier 180 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | http-console 2 | ============ 3 | 4 | > Speak HTTP like a local 5 | 6 | Talking to an HTTP server with `curl` can be fun, but most of the time it's a `PITA`. 7 | 8 | `http-console` is a simple and intuitive interface for speaking the HTTP protocol. 9 | 10 | *PS: HTTP has never been this much fun.* 11 | 12 | synopsis 13 | -------- 14 | 15 | ![http-console](http://dl.dropbox.com/u/251849/http-console.png) 16 | 17 | installation 18 | ------------ 19 | 20 | *http-console* was written for [node](http://nodejs.org), so make sure you have that installed 21 | first. Then you need [npm](http://github.com/isaacs/npm), node's package manager. 22 | 23 | Once you're all set, to install globally, run: 24 | 25 | $ npm install http-console -g 26 | 27 | It'll download the dependencies, and install the command-line tool in `/usr/local/bin` 28 | (you may need to make use of `sudo` or equivalent to gain access). 29 | 30 | ### Installing the bleeding edge # 31 | 32 | The latest release will often be available on npm as `http-console@latest`, so you can run: 33 | 34 | $ npm install http-console@latest 35 | 36 | Alternatively, you can download a tarball of this repo, or clone it. Just make sure you have 37 | the latest version of node. 38 | 39 | introduction 40 | ------------ 41 | 42 | Let's assume we have a [CouchDB](http://couchdb.apache.org) instance running locally. 43 | 44 | ### connecting # 45 | 46 | To connect, we run `http-console`, passing it the server host and port as such: 47 | 48 | $ http-console 127.0.0.1:5984 49 | 50 | ### navigating # 51 | 52 | Once connected, we should see the *http prompt*: 53 | 54 | http://127.0.0.1:5984/> 55 | 56 | server navigation is similar to directory navigation, except a little simpler: 57 | 58 | http://127.0.0.1:5984/> /logs 59 | http://127.0.0.1:5984/logs> /46 60 | http://127.0.0.1:5984/logs/46> .. 61 | http://127.0.0.1:5984/logs> .. 62 | http://127.0.0.1:5984/> 63 | 64 | ### requesting # 65 | 66 | HTTP requests are issued with the HTTP verbs *GET*, *PUT*, *POST*, *HEAD* and *DELETE*, and 67 | a relative path: 68 | 69 | http://127.0.0.1:5984/> GET / 70 | HTTP/1.1 200 OK 71 | Date: Mon, 31 May 2010 04:43:39 GMT 72 | Content-Length: 41 73 | 74 | { 75 | couchdb: "Welcome", 76 | version: "0.11.0" 77 | } 78 | 79 | http://127.0.0.1:5984/> GET /bob 80 | HTTP/1.1 404 Not Found 81 | Date: Mon, 31 May 2010 04:45:32 GMT 82 | Content-Length: 44 83 | 84 | { 85 | error: "not_found", 86 | reason: "no_db_file" 87 | } 88 | 89 | When issuing *POST* and *PUT* commands, we have the opportunity to send data too: 90 | 91 | http://127.0.0.1:5984/> /rabbits 92 | http://127.0.0.1:5984/rabbits> POST 93 | ... {"name":"Roger"} 94 | 95 | HTTP/1.1 201 Created 96 | Location: http://127.0.0.1/rabbits/2fd9db055885e6982462a10e54003127 97 | Date: Mon, 31 May 2010 05:09:15 GMT 98 | Content-Length: 95 99 | 100 | { 101 | ok: true, 102 | id: "2fd9db055885e6982462a10e54003127", 103 | rev: "1-0c3db91854f26486d1c3922f1a651d86" 104 | } 105 | 106 | Make sure you have your `Content-Type` header set properly, if the API requires it. More 107 | in the section below. 108 | 109 | > Note that if you're trying to POST to a form handler, you'll most probably want to send data 110 | in `multipart/form-data` format, such as `name=roger&hair=black`. http-console sends your POST/PUT data *as is*, 111 | so make sure you've got the format right, and the appropriate `Content-Type` header. 112 | 113 | ### setting headers # 114 | 115 | Sometimes, it's useful to set HTTP headers: 116 | 117 | http://127.0.0.1:5984/> Accept: application/json 118 | http://127.0.0.1:5984/> X-Lodge: black 119 | 120 | These headers are sent with all requests in this session. To see all active headers, 121 | run the `.headers` command: 122 | 123 | http://127.0.0.1:5984/> .headers 124 | Accept: application/json 125 | X-Lodge: black 126 | 127 | Removing headers is just as easy: 128 | 129 | http://127.0.0.1:5984/> Accept: 130 | http://127.0.0.1:5984/> .headers 131 | X-Lodge: black 132 | 133 | Because JSON is such a common data format, http-console has a way to automatically set 134 | the `Content-Type` header to `application/json`. Just pass the `--json` option when 135 | starting http-console, or run the `.json` command: 136 | 137 | $ http-console 127.0.0.1:5984 --json 138 | http://127.0.0.1:5984/> .headers 139 | Accept: */* 140 | Content-Type: application/json 141 | 142 | ### cookies # 143 | 144 | You can enable cookie tracking with the `--cookies` option flag. 145 | To see what cookies are stored, use the `.cookies` command. 146 | 147 | ### SSL # 148 | 149 | To enable SSL, pass the `--ssl` flag, or specify the address with `https`. 150 | 151 | ### quitting # 152 | 153 | http://127.0.0.1:5984/> .q 154 | 155 | nuff' said. 156 | 157 | 158 | 159 | -------------------------------------------------------------------------------- /bin/http-console: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | var path = require('path'), 4 | sys = require('util'); 5 | 6 | var httpConsole = require('../lib/http-console'); 7 | 8 | var help = [ 9 | 'usage: http-console [username:password@host:port] [options]', 10 | '', 11 | 'options:', 12 | ' --cookies remember cookies', 13 | ' --no-cookies do not remember cookies', 14 | '-v, --verbose print requests', 15 | ' --json set "Content-Type" header to application/json', 16 | ' --notimeout don\'t timeout requests', 17 | ' --ssl create a secure connection', 18 | ' --version print version', 19 | '-h, --help display this message' 20 | ].join('\n'); 21 | 22 | var argv = process.argv.slice(2), args = [], 23 | arg, option, 24 | options = { 25 | rememberCookies: true, 26 | json: false, 27 | timeout: true, 28 | verbose: false 29 | }; 30 | 31 | while (arg = argv.shift()) { 32 | if (option = arg.match(/^--?([\w-]+)$/)) { 33 | switch (option[1]) { 34 | case 'cookies': 35 | options.rememberCookies = true; 36 | break; 37 | case 'no-cookies': 38 | options.rememberCookies = false; 39 | break; 40 | case 'ssl': 41 | options.useSSL = true; 42 | break; 43 | case 'v': 44 | case 'verbose': 45 | options.verbose = true; 46 | break; 47 | case 'version': 48 | sys.print('http-console v' + httpConsole.version); 49 | process.exit(0); 50 | case 'json': 51 | options.json = true; 52 | break; 53 | case 'notimeout': 54 | options.timeout = false; 55 | break; 56 | case 'h': 57 | case 'help': 58 | sys.print(help); 59 | process.exit(0); 60 | default: 61 | sys.print("http-console: unrecognized option '" + option[0] + "'"); 62 | process.exit(1); 63 | } 64 | } else { 65 | args.push(arg); 66 | } 67 | } 68 | 69 | var url = args.shift() || 'http://127.0.0.1:8080', 70 | protocol = url.match(/^(https?)?/)[0] || 'http'; 71 | 72 | options.useSSL = protocol === 'https'; 73 | 74 | url = url.replace(protocol + '://', '').split('@'); 75 | 76 | var hostPort = url.pop().split(':'), 77 | auth = url.pop(); 78 | 79 | if (auth) { 80 | auth = auth.split(':'); 81 | options.auth = { 82 | username: auth.shift(), 83 | password: auth.shift() 84 | }; 85 | } 86 | 87 | var host = hostPort.shift(), 88 | port = hostPort.shift() || (options.useSSL ? 443 : 80); 89 | 90 | var console = new(httpConsole.Console)(host, port, options); 91 | 92 | console.initialize(); 93 | 94 | -------------------------------------------------------------------------------- /lib/ext/index.js: -------------------------------------------------------------------------------- 1 | 2 | ['bold', 'grey', 'yellow', 'red', 'green', 'white', 'cyan'].forEach(function (style) { 3 | Object.defineProperty(String.prototype, style, { 4 | get: function () { 5 | return stylize(this, style); 6 | } 7 | }); 8 | }); 9 | 10 | function stylize(str, style) { 11 | var styles = { 12 | 'bold' : [1, 22], 13 | 'italic' : [3, 23], 14 | 'underline' : [4, 24], 15 | 'yellow' : [33, 39], 16 | 'cyan' : [36, 39], 17 | 'white' : [37, 39], 18 | 'green' : [32, 39], 19 | 'red' : [31, 39], 20 | 'grey' : [90, 39], 21 | }; 22 | return '\033[' + styles[style][0] + 'm' + str + 23 | '\033[' + styles[style][1] + 'm'; 24 | } 25 | -------------------------------------------------------------------------------- /lib/http-console.js: -------------------------------------------------------------------------------- 1 | 2 | const http = require('http'), 3 | https = require('https'), 4 | fs = require('fs'), 5 | queryString = require('querystring'), 6 | readline = require('readline'), 7 | process = require('process'), 8 | sys = require('util'); 9 | 10 | require('./ext'); 11 | 12 | let inspect = null; 13 | 14 | try { 15 | inspect = require('eyes').inspector({ maxLength: -1 }); 16 | } catch (e) { 17 | inspect = function (obj) { console.log(sys.inspect(obj).white) } 18 | } 19 | 20 | var consoles = []; 21 | 22 | this.Console = function (host, port, options) { 23 | this.host = host; 24 | this.port = parseInt(port); 25 | this.options = options; 26 | this.timeout = this.options.timeout ? 5000 : 0; 27 | this.path = []; 28 | this.socket = null; 29 | this.cookies = {}; 30 | consoles.push(this); 31 | }; 32 | 33 | this.Console.prototype = new(function () { 34 | this.initialize = function () { 35 | var that = this; 36 | 37 | this.welcome(); 38 | 39 | this.headers = { 'Accept': '*/*' }; 40 | 41 | if (this.options.json) { 42 | this.headers['Accept'] = 'application/json'; 43 | this.headers['Content-Type'] = 'application/json'; 44 | } 45 | 46 | if (this.options.auth) { 47 | this.headers['Authorization'] = "Basic " + 48 | Buffer.from(this.options.auth.username + ':' + this.options.auth.password).toString('base64'); 49 | } 50 | 51 | this.readline = readline.createInterface(process.stdin, process.stdout); 52 | 53 | this.readline.on('line', function (cmd) { 54 | that.exec(cmd.trim()); 55 | }).on('close', function () { 56 | process.stdout.write('\n'); 57 | process.exit(0); 58 | }); 59 | 60 | this.prompt(); 61 | 62 | return this; 63 | }; 64 | this.welcome = function () { 65 | process.stdout.write("> " + ("http-console " + exports.version).bold + '\n' + 66 | "> Welcome, enter .help if you're lost." + '\n' + 67 | "> Connecting to " + this.host + " on port " + this.port + '.\n\n'); 68 | }; 69 | this.request = function (method, path, headers, callback) { 70 | var request, that = this; 71 | 72 | this.headers['Host'] = this.host; 73 | 74 | for (var k in this.headers) { headers[k] = this.headers[k] } 75 | 76 | method = method.toUpperCase(); 77 | path = encodeURI(path); 78 | 79 | if (this.options.verbose) { 80 | console.log('> ' + (method + ' ' + path).grey); 81 | } 82 | 83 | this.setCookies(headers); 84 | 85 | request = (this.options.useSSL ? https : http).request({ 86 | host: that.host, 87 | port: that.port, 88 | method: method, 89 | path: path, 90 | headers: headers 91 | }, function (res) { 92 | var body = ""; 93 | 94 | res.setEncoding('utf8'); 95 | 96 | if (that.options.rememberCookies) { that.rememberCookies(res.headers) } 97 | res.on('data', function (chunk) { body += chunk }); 98 | res.on('end', function () { callback(res, body) }); 99 | }).on('error', function (e) { 100 | console.error(e.toString().red); 101 | that.prompt(); 102 | }); 103 | 104 | return request; 105 | }; 106 | this.setCookies = function (headers) { 107 | const that = this; 108 | const keys = Object.keys(this.cookies); 109 | 110 | if (keys.length) { 111 | const header = keys.filter(function (k) { 112 | var options = that.cookies[k].options; 113 | return (!options.expires || options.expires >= Date.now()) && 114 | (!options.path || ('/' + that.path.join('/')).match(new(RegExp)('^' + options.path))); 115 | }).map(function (k) { 116 | return [k, queryString.escape(that.cookies[k].value) || ''].join('='); 117 | }).join(', '); 118 | header && (headers['Cookie'] = header); 119 | } 120 | }; 121 | this.exec = function (command) { 122 | var method, headers = {}, path = this.path, 123 | that = this, 124 | match, req; 125 | 126 | if (this.pending) { 127 | req = this.request(this.pending.method, this.pending.path, { 128 | 'Content-Length' : command.length 129 | }, function (res, body) { 130 | that.printResponse(res, body, function () { 131 | that.prompt(); 132 | }); 133 | }); 134 | req.write(command); 135 | req.end(); 136 | 137 | return this.pending = null; 138 | } else if (command[0] === '/') { 139 | if (command === '//') { 140 | this.path = []; 141 | } else { 142 | Array.prototype.push.apply( 143 | this.path, command.slice(1).split('/') 144 | ); 145 | } 146 | } else if (command === '..') { 147 | this.path.pop(); 148 | } else if (command[0] === '.') { 149 | switch (command.slice(1)) { 150 | case 'h': 151 | case 'headers': 152 | exports.merge(headers, this.headers); 153 | this.setCookies(headers); 154 | this.printHeaders(headers); 155 | break; 156 | case 'default-headers': 157 | this.printHeaders(this.headers); 158 | break; 159 | case 'o': 160 | case 'options': 161 | inspect(this.options); 162 | break; 163 | case 'c': 164 | case 'cookies': 165 | inspect(this.cookies); 166 | break; 167 | case 'help': 168 | console.log(exports.help); 169 | break; 170 | case 'j': 171 | case 'json': 172 | this.headers['Content-Type'] = 'application/json'; 173 | break; 174 | case 'exit': 175 | case 'quit': 176 | case 'q': 177 | process.exit(0); 178 | } 179 | } else if (command[0] === '\\') { 180 | this.exec(command.replace(/^\\/, '.')); 181 | } else if (match = command.match(/^([a-zA-Z-]+):\s*(.*)/)) { 182 | if (match[2]) { 183 | this.headers[match[1]] = match[2]; 184 | } else { 185 | delete(this.headers[match[1]]); 186 | } 187 | } else if (/^(GET|POST|PUT|PATCH|HEAD|DELETE|OPTIONS)/i.test(command)) { 188 | command = command.split(/\s+/); 189 | method = command.shift().toUpperCase(); 190 | path = this.path.slice(0); 191 | 192 | if (command.length > 0) { path.push(command[0]) } 193 | 194 | path = ('/' + path.join('/')).replace(/\/+/g, '/'); 195 | 196 | if (method === 'PUT' || method === 'POST') { 197 | this.pending = { method: method, path: path }; 198 | this.dataPrompt(); 199 | } else { 200 | this.request(method, path, {}, function (res, body) { 201 | that.printResponse.call(that, res, body, function () { 202 | that.prompt(); 203 | }); 204 | }).end(); 205 | } 206 | return; 207 | } else if (command) { 208 | console.log(("unknown command '" + command + "'").yellow.bold); 209 | } 210 | this.prompt(); 211 | }; 212 | this.printResponse = function (res, body, callback) { 213 | var status = ('HTTP/' + res.httpVersion + 214 | ' ' + res.statusCode + 215 | ' ' + http.STATUS_CODES[res.statusCode]).bold, output; 216 | 217 | if (res.statusCode >= 500) { status = status.red } 218 | else if (res.statusCode >= 400) { status = status.yellow } 219 | else if (res.statusCode >= 300) { status = status.cyan } 220 | else { status = status.green } 221 | 222 | console.log(status); 223 | 224 | this.printHeaders(res.headers); 225 | 226 | console.log(); 227 | 228 | try { output = JSON.parse(body) } 229 | catch (_) { output = body.trim() } 230 | 231 | if (typeof(output) === 'string') { 232 | output.length > 0 && console.log(output.white); 233 | } else { 234 | inspect(output); 235 | } 236 | 237 | // Make sure the buffer is flushed before 238 | // we display the prompt. 239 | if (process.stdout.write('')) { 240 | callback(); 241 | } else { 242 | process.stdout.on('drain', function () { 243 | callback(); 244 | }); 245 | } 246 | }; 247 | this.prompt = function () { 248 | var protocol = this.options.useSSL ? 'https://' : 'http://', 249 | path = '/' + this.path.join('/'), 250 | host = this.host + ':' + this.port, 251 | arrow = '> '; 252 | 253 | var length = (protocol + host + path + arrow).length; 254 | 255 | this.readline.setPrompt((protocol + host).grey + path + arrow.grey, length); 256 | this.readline.prompt(); 257 | }; 258 | this.dataPrompt = function () { 259 | var prompt = '... '; 260 | this.readline.setPrompt(prompt.grey, prompt.length); 261 | this.readline.prompt(); 262 | }; 263 | this.printHeaders = function (headers) { 264 | Object.keys(headers).forEach(function (k) { 265 | var key = k.replace(/\b([a-z])/g, function (_, m) { 266 | return m.toUpperCase(); 267 | }).bold; 268 | console.log(key + ': ' + headers[k]); 269 | }); 270 | }; 271 | this.rememberCookies = function (headers) { 272 | var that = this; 273 | var parts, cookie, name, value; 274 | 275 | if ('set-cookie' in headers) { 276 | headers['set-cookie'].forEach(function (c) { 277 | parts = c.split(/; */); 278 | cookie = parts.shift().match(/^(.+?)=(.*)$/).slice(1); 279 | name = cookie[0]; 280 | value = queryString.unescape(cookie[1]); 281 | 282 | cookie = that.cookies[name] = { 283 | value: value, 284 | options: {} 285 | }; 286 | 287 | parts.forEach(function (part) { 288 | part = part.split('='); 289 | cookie.options[part[0]] = part.length > 1 ? part[1] : true; 290 | }); 291 | 292 | if (cookie.options.expires) { 293 | cookie.options.expires = new(Date)(cookie.options.expires); 294 | } 295 | }); 296 | } 297 | }; 298 | }); 299 | 300 | this.version = fs.readFileSync(require('path').join(__dirname, '..', 'package.json')) 301 | .toString().match(/"version"\s*:\s*"([\d.]+)"/)[1]; 302 | 303 | this.help = [ 304 | '.h[eaders] ' + 'show active request headers.'.grey, 305 | '.o[ptions] ' + 'show options.'.grey, 306 | '.c[ookies] ' + 'show client cookies.'.grey, 307 | '.j[son] ' + 'set \'Content-Type\' header to \'application/json\'.'.grey, 308 | '.help ' + 'display this message.'.grey, 309 | '.q[uit] ' + 'exit console.'.grey 310 | ].join('\n'); 311 | 312 | this.merge = function (target /*, objects... */) { 313 | var args = Array.prototype.slice.call(arguments, 1); 314 | 315 | args.forEach(function (a) { 316 | var keys = Object.keys(a); 317 | for (var i = 0; i < keys.length; i++) { 318 | target[keys[i]] = a[keys[i]]; 319 | } 320 | }); 321 | return target; 322 | }; 323 | 324 | process.on('uncaughtException', function (e) { 325 | console.log(e.stack.red); 326 | consoles[consoles.length - 1].prompt(); 327 | }); 328 | 329 | process.on('exit', function () { 330 | consoles.forEach(function (c) { 331 | // TODO: Cleanup 332 | }); 333 | console.log(); 334 | }); 335 | 336 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name" : "http-console", 3 | "description" : "Speak HTTP like a local", 4 | "url" : "https://github.com/cloudhead/http-console", 5 | "keywords" : ["http", "console", "repl"], 6 | "author" : "Alexis Sellier ", 7 | "contributors" : [], 8 | "dependencies" : { "eyes": ">=0.1.4" }, 9 | "version" : "1.0.0", 10 | "main" : "./lib/http-console", 11 | "directories" : { "lib": "./lib", "test": "./test" }, 12 | "bin" : { "http-console": "./bin/http-console" }, 13 | "engines" : { "node": ">=0.12.0" }, 14 | "licenses" : [{ "type": "Apache 2.0", "url": "https://github.com/cloudhead/http-console/blob/master/LICENSE" }], 15 | "repository" : { "type": "git", "url": "https://github.com/cloudhead/http-console.git" } 16 | } 17 | --------------------------------------------------------------------------------