├── .editorconfig ├── .gitignore ├── .jshintrc ├── .travis.yml ├── LICENSE ├── README.md ├── gulpfile.js ├── lib ├── apistatus.js └── codes.js ├── package.json └── test ├── har.json └── test.js /.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | charset = utf-8 9 | trim_trailing_whitespace = true 10 | insert_final_newline = true 11 | 12 | [*.md] 13 | trim_trailing_whitespace = false 14 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.log 2 | node_modules 3 | coverage* 4 | .DS_Store -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "asi": true, 3 | "node": true 4 | } 5 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | 3 | node_js: 4 | - iojs 5 | - 0.12 6 | - 0.11 7 | - 0.10 8 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Mashape, https://www.mashape.com 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # API Status [![Test Status](https://img.shields.io/travis/Mashape/apistatus.svg)](https://travis-ci.org/Mashape/apistatus/) [![Test Coverage](https://codeclimate.com/github/Mashape/apistatus/badges/coverage.svg)](https://codeclimate.com/github/Mashape/apistatus) ![License](https://img.shields.io/npm/l/apistatus.svg) 2 | 3 | API status is a simple tool to send API requests and retrieve standard response data. 4 | 5 | ### Install 6 | 7 | ```sh 8 | npm install apistatus 9 | ``` 10 | 11 | ### Usage 12 | 13 | ```js 14 | var apistatus = require('apistatus') 15 | 16 | apistatus('http://mockbin.com/status/200', function(status){ 17 | console.log(status) 18 | // { online: true, statusCode: 200, category: 'Success', message: 'OK' } 19 | }) 20 | 21 | apistatus('http://mockbin.com/status/404', function(status){ 22 | console.log(status) 23 | // { online: true, statusCode: 404, category: 'Client Error', message: 'Not Found' } 24 | }) 25 | 26 | apistatus('http://notarealdomain35252.com/', function(status){ 27 | console.log(status) 28 | // { online: false } 29 | }) 30 | 31 | apistatus(require("har.json"), function(statuses){ 32 | console.log(statuses) 33 | // [{ online: true, statusCode: 200, category: 'Success', message: 'OK' },... 34 | }) 35 | ``` 36 | 37 | ### Wishlist 38 | 39 | - optionally use the HAR response object to check against, essentially automated API testing 40 | 41 | ### Contributing 42 | 43 | Forks and pull requests are most welcomed. Please run `npm test` before sending a pull request. 44 | 45 | ### MIT license 46 | 47 | Copyright (c) 2015, Mashape (https://www.mashape.com/) 48 | -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | var gulp = require('gulp') 2 | var jshint = require('gulp-jshint') 3 | var mocha = require('gulp-mocha') 4 | 5 | var codes = 'lib/**/*.js' 6 | 7 | gulp.task('hint', function() { 8 | return gulp.src(codes) 9 | .pipe(jshint()) 10 | .pipe(jshint.reporter('default')) 11 | }) 12 | 13 | gulp.task('test', function () { 14 | return gulp.src('test.js', {read: false}) 15 | .pipe(mocha({reporter: 'nyan'})) 16 | }) 17 | 18 | gulp.task('watch', function() { 19 | gulp.watch(codes, ['hint', 'test']) 20 | gulp.watch('test.js', ['test']) 21 | }) 22 | 23 | gulp.task('default', ['hint', 'test', 'watch']) 24 | -------------------------------------------------------------------------------- /lib/apistatus.js: -------------------------------------------------------------------------------- 1 | var unirest = require('unirest') 2 | var harplayer = require('harplayer') 3 | var statusCodes = require('./codes.js') 4 | 5 | function statusCategory(code) { 6 | if (code >= 100 && code < 200) { 7 | return "Informational" 8 | } else if (code >= 200 && code < 300) { 9 | return "Success" 10 | } else if (code >= 300 && code < 400) { 11 | return "Redirect" 12 | } else if (code >= 400 && code < 500) { 13 | return "Client Error" 14 | } else if (code >= 500 && code < 600) { 15 | return "Server Error" 16 | } else { 17 | return "Non-standard Category" 18 | } 19 | } 20 | 21 | function statusMessage(code) { 22 | return statusCodes[code] || "Non-standard Status" 23 | } 24 | 25 | function buildHandler(start) { 26 | return function(code) { 27 | var output = {} 28 | if (code && typeof code === 'number') { 29 | output.online = true 30 | output.latency = new Date().getTime() - start 31 | output.statusCode = code 32 | output.message = statusMessage(code) 33 | output.category = statusCategory(code) 34 | } else { 35 | output.online = false 36 | } 37 | return output 38 | } 39 | } 40 | 41 | function getStatus(target, callback) { 42 | 43 | // Build a new response hander with the current time 44 | // so we can calculate the latency when it's all over 45 | var buildResponse = buildHandler(new Date().getTime()) 46 | 47 | // Just in case the user doesn't care about the response 48 | // let's define a fallback callback right here 49 | if (!callback) { 50 | callback = function(){} 51 | } 52 | 53 | // We might have a HAR, so let's try to replay 54 | // all the entries and return an array with the 55 | // responses in sequential order 56 | if (typeof target === 'object') { 57 | var data = [] 58 | harplayer.replayAll(target, function(err, res, body){ 59 | if (err) { 60 | callback(null, err) 61 | } 62 | data.push(buildResponse(res.statusCode)) 63 | if (data.length === target.log.entries.length) { 64 | callback(data) 65 | } 66 | }) 67 | } else if (typeof target === 'string') { 68 | 69 | // Create our request object with the url and 70 | // a long timoeout, not following redirects then 71 | // put the request into our response handler 72 | unirest 73 | .get(target) 74 | .timeout(30000) 75 | .followRedirect(false) 76 | .end(function responseHandler(res) { 77 | var data = buildResponse(res.status) 78 | callback(data) 79 | }) 80 | } else { 81 | throw new Error("Missing valid target URL") 82 | } 83 | 84 | } 85 | 86 | module.exports = getStatus 87 | -------------------------------------------------------------------------------- /lib/codes.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | 100: "Continue", 3 | 101: "Switching Protocols", 4 | 200: "OK", 5 | 201: "Created", 6 | 202: "Accepted", 7 | 203: "Non-Authoritative Information", 8 | 204: "No Content", 9 | 205: "Reset Content", 10 | 206: "Partial Content", 11 | 300: "Multiple Choices", 12 | 301: "Moved Permanently", 13 | 302: "Found", 14 | 303: "See Other", 15 | 304: "Not Modified", 16 | 305: "Use Proxy", 17 | 307: "Temporary Redirect", 18 | 400: "Bad Request", 19 | 401: "Unauthorized", 20 | 402: "Payment Required", 21 | 403: "Forbidden", 22 | 404: "Not Found", 23 | 405: "Method Not Allowed", 24 | 406: "Not Acceptable", 25 | 407: "Proxy Authentication Required", 26 | 408: "Request Timeout", 27 | 409: "Conflict", 28 | 410: "Gone", 29 | 411: "Length Required", 30 | 412: "Precondition Failed", 31 | 413: "Payload Too Large", 32 | 414: "URI Too Long", 33 | 415: "Unsupported Media Type", 34 | 416: "Range Not Satisfiable", 35 | 417: "Expectation Failed", 36 | 418: "I'm a teapot", 37 | 426: "Upgrade Required", 38 | 500: "Internal Server Error", 39 | 501: "Not Implemented", 40 | 502: "Bad Gateway", 41 | 503: "Service Unavailable", 42 | 504: "Gateway Time-out", 43 | 505: "HTTP Version Not Supported", 44 | 102: "Processing", 45 | 207: "Multi-Status", 46 | 226: "IM Used", 47 | 308: "Permanent Redirect", 48 | 422: "Unprocessable Entity", 49 | 423: "Locked", 50 | 424: "Failed Dependency", 51 | 428: "Precondition Required", 52 | 429: "Too Many Requests", 53 | 431: "Request Header Fields Too Large", 54 | 451: "Unavailable For Legal Reasons", 55 | 506: "Variant Also Negotiates", 56 | 507: "Insufficient Storage", 57 | 511: "Network Authentication Required" 58 | } 59 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "apistatus", 3 | "version": "0.3.0", 4 | "description": "Returns the status of an HTTP request.", 5 | "main": "lib/apistatus.js", 6 | "scripts": { 7 | "test": "mocha", 8 | "coverage": "istanbul cover _mocha -- -R spec", 9 | "codeclimate-report-coverage": "codeclimate < coverage/lcov.info" 10 | }, 11 | "author": "Mashape", 12 | "license": "MIT", 13 | "dependencies": { 14 | "harplayer": "^0.2.0", 15 | "unirest": "^0.4.0" 16 | }, 17 | "devDependencies": { 18 | "codeclimate-test-reporter": "0.0.4", 19 | "gulp": "^3.8.11", 20 | "gulp-jshint": "^1.9.4", 21 | "gulp-mocha": "^2.0.0", 22 | "istanbul": "^0.3.13", 23 | "mocha": "^2.0.1", 24 | "nock": "^0.50.0" 25 | }, 26 | "repository": { 27 | "type": "git", 28 | "url": "https://github.com/mashape/apistatus.git" 29 | }, 30 | "keywords": [ 31 | "api", 32 | "status", 33 | "http", 34 | "get", 35 | "har", 36 | "codes" 37 | ], 38 | "bugs": { 39 | "url": "https://github.com/mashape/apistatus/issues" 40 | }, 41 | "homepage": "https://github.com/mashape/apistatus" 42 | } 43 | -------------------------------------------------------------------------------- /test/har.json: -------------------------------------------------------------------------------- 1 | { 2 | "log": { 3 | "version": "1.2", 4 | "creator": { 5 | "name": "WebInspector", 6 | "version": "537.36" 7 | }, 8 | "pages": [ 9 | { 10 | "startedDateTime": "2015-04-02T05:47:13.818Z", 11 | "id": "page_1", 12 | "title": "https://www.mashape.com/", 13 | "pageTimings": { 14 | "onContentLoad": 1738.1799221038818, 15 | "onLoad": 3852.989912033081 16 | } 17 | } 18 | ], 19 | "entries": [ 20 | { 21 | "startedDateTime": "2015-04-02T05:47:13.818Z", 22 | "time": 554.1799068450928, 23 | "request": { 24 | "method": "GET", 25 | "url": "http://localhost:9876/ok", 26 | "httpVersion": "HTTP/1.1", 27 | "headers": [], 28 | "queryString": [], 29 | "cookies": [], 30 | "headersSize": 1047, 31 | "bodySize": 0 32 | }, 33 | "response": { 34 | "status": 200, 35 | "statusText": "OK", 36 | "httpVersion": "HTTP/1.1", 37 | "headers": [], 38 | "cookies": [], 39 | "content": { 40 | "size": 58331, 41 | "mimeType": "text/html", 42 | "compression": 44637 43 | }, 44 | "redirectURL": "", 45 | "headersSize": 705, 46 | "bodySize": 13694, 47 | "_transferSize": 14399 48 | }, 49 | "cache": {}, 50 | "timings": { 51 | "blocked": 0, 52 | "dns": 0, 53 | "connect": 240.710999998555, 54 | "send": 0.054999996791991634, 55 | "wait": 309.74000000423996, 56 | "receive": 3.673906845505826, 57 | "ssl": 240.643999997701 58 | }, 59 | "connection": "702", 60 | "pageref": "page_1" 61 | }, 62 | { 63 | "startedDateTime": "2015-04-02T05:47:13.818Z", 64 | "time": 554.1799068450928, 65 | "request": { 66 | "method": "GET", 67 | "url": "http://localhost:9876/redirect", 68 | "httpVersion": "HTTP/1.1", 69 | "headers": [], 70 | "queryString": [], 71 | "cookies": [], 72 | "headersSize": 1047, 73 | "bodySize": 0 74 | }, 75 | "response": { 76 | "status": 200, 77 | "statusText": "OK", 78 | "httpVersion": "HTTP/1.1", 79 | "headers": [], 80 | "cookies": [], 81 | "content": { 82 | "size": 58331, 83 | "mimeType": "text/html", 84 | "compression": 44637 85 | }, 86 | "redirectURL": "", 87 | "headersSize": 705, 88 | "bodySize": 13694, 89 | "_transferSize": 14399 90 | }, 91 | "cache": {}, 92 | "timings": { 93 | "blocked": 0, 94 | "dns": 0, 95 | "connect": 240.710999998555, 96 | "send": 0.054999996791991634, 97 | "wait": 309.74000000423996, 98 | "receive": 3.673906845505826, 99 | "ssl": 240.643999997701 100 | }, 101 | "connection": "702", 102 | "pageref": "page_1" 103 | }, 104 | { 105 | "startedDateTime": "2015-04-02T05:47:13.818Z", 106 | "time": 554.1799068450928, 107 | "request": { 108 | "method": "GET", 109 | "url": "http://localhost:9876/fail", 110 | "httpVersion": "HTTP/1.1", 111 | "headers": [], 112 | "queryString": [], 113 | "cookies": [], 114 | "headersSize": 1047, 115 | "bodySize": 0 116 | }, 117 | "response": { 118 | "status": 200, 119 | "statusText": "OK", 120 | "httpVersion": "HTTP/1.1", 121 | "headers": [], 122 | "cookies": [], 123 | "content": { 124 | "size": 58331, 125 | "mimeType": "text/html", 126 | "compression": 44637 127 | }, 128 | "redirectURL": "", 129 | "headersSize": 705, 130 | "bodySize": 13694, 131 | "_transferSize": 14399 132 | }, 133 | "cache": {}, 134 | "timings": { 135 | "blocked": 0, 136 | "dns": 0, 137 | "connect": 240.710999998555, 138 | "send": 0.054999996791991634, 139 | "wait": 309.74000000423996, 140 | "receive": 3.673906845505826, 141 | "ssl": 240.643999997701 142 | }, 143 | "connection": "702", 144 | "pageref": "page_1" 145 | } 146 | ] 147 | } 148 | } -------------------------------------------------------------------------------- /test/test.js: -------------------------------------------------------------------------------- 1 | var apistatus = require('../lib/apistatus.js') 2 | var statusCodes = require('../lib/codes.js') 3 | var assert = require('assert') 4 | var nock = require('nock'); 5 | 6 | // Mock http responses 7 | var nockServer = nock('http://localhost:9876'); 8 | 9 | nockServer.get('/ok').reply(200, undefined).persist(); 10 | nockServer.get('/fail').reply(501, undefined).persist(); 11 | nockServer.get('/redirect').reply(301, undefined).persist(); 12 | nockServer.get('/123').reply(123, undefined).persist(); 13 | nockServer.get('/500').reply(500, undefined).persist(); 14 | nockServer.get('/501').reply(501, undefined).persist(); 15 | nockServer.get('/777').reply(777, undefined).persist(); 16 | 17 | nockServer.get('/404').reply(404, undefined, { 18 | Location: 'http://localhost:9876/ok' 19 | }).persist(); 20 | 21 | nockServer.get('/301').reply(301, undefined, { 22 | Location: 'http://localhost:9876/ok' 23 | }).get('/302').reply(302, undefined, { 24 | Location: 'http://localhost:9876/ok' 25 | }) 26 | 27 | describe('Package', function () { 28 | it('should still work with missing callback', function(){ 29 | assert.doesNotThrow(function() { 30 | apistatus('http://localhost:9876/ok') 31 | }, Error) 32 | }) 33 | it('should throw error for missing URL', function(done){ 34 | assert.throws(function() { 35 | apistatus(function(){}) 36 | }, Error) 37 | done() 38 | }) 39 | it('should throw error for invalid URL', function(){ 40 | assert.throws(function() { 41 | apistatus(53, function(){}) 42 | }, Error) 43 | }) 44 | }) 45 | 46 | describe('Online', function () { 47 | it('should return online true', function(done){ 48 | apistatus('http://localhost:9876/ok', function(status){ 49 | assert.equal(status.online, true) 50 | done() 51 | }) 52 | }) 53 | it('should return message "OK"', function(done){ 54 | apistatus('http://localhost:9876/ok', function(status){ 55 | assert.equal(status.message, "OK") 56 | done() 57 | }) 58 | }) 59 | it('should return statusCode 200', function(done){ 60 | apistatus('http://localhost:9876/ok', function(status){ 61 | assert.equal(status.statusCode, 200) 62 | done() 63 | }) 64 | }) 65 | }) 66 | 67 | describe('Offline', function () { 68 | it('should return online false', function(done){ 69 | apistatus('http://nodomain:9876/', function(status){ 70 | assert.equal(status.online, false) 71 | done() 72 | }) 73 | }) 74 | it('should return undefined statusDescription', function(done){ 75 | apistatus('http://nodomain:9876/', function(status){ 76 | assert.equal(status.statusDescription, undefined) 77 | done() 78 | }) 79 | }) 80 | it('should return undefined status', function(done){ 81 | apistatus('http://nodomain:9876/', function(status){ 82 | assert.equal(status.code, undefined) 83 | done() 84 | }) 85 | }) 86 | }) 87 | 88 | describe('Redirects', function () { 89 | it('should not follow 301 redirects', function(done){ 90 | apistatus('http://localhost:9876/301', function(status){ 91 | assert.equal(status.statusCode, 301) 92 | done() 93 | }) 94 | }) 95 | it('should not follow 302 redirects', function(done){ 96 | apistatus('http://localhost:9876/302', function(status){ 97 | assert.equal(status.statusCode, 302) 98 | done() 99 | }) 100 | }) 101 | it('should not follow 404 redirect', function(done){ 102 | apistatus('http://localhost:9876/404', function(status){ 103 | assert.equal(status.statusCode, 404) 104 | done() 105 | }) 106 | }) 107 | }) 108 | 109 | describe('Errors', function () { 110 | it('should return client errors', function(done){ 111 | apistatus('http://localhost:9876/404', function(status){ 112 | assert.equal(status.category, "Client Error") 113 | done() 114 | }) 115 | }) 116 | it('should return server errors', function(done){ 117 | apistatus('http://localhost:9876/501', function(status){ 118 | assert.equal(status.category, "Server Error") 119 | done() 120 | }) 121 | }) 122 | it('should return message "Not Found"', function(done){ 123 | apistatus('http://localhost:9876/404', function(status){ 124 | assert.equal(status.message, "Not Found") 125 | done() 126 | }) 127 | }) 128 | it('should return status "404"', function(done){ 129 | apistatus('http://localhost:9876/404', function(status){ 130 | assert.equal(status.statusCode, "404") 131 | done() 132 | }) 133 | }) 134 | it('should not follow 404 redirect', function(done){ 135 | apistatus('http://localhost:9876/404', function(status){ 136 | assert.equal(status.statusCode, 404) 137 | done() 138 | }) 139 | }) 140 | }) 141 | 142 | describe('Status Codes', function () { 143 | it('should be an object', function(){ 144 | assert.equal(typeof statusCodes, "object") 145 | }) 146 | it('should return status "123"', function(done){ 147 | apistatus('http://localhost:9876/123', function(status){ 148 | assert.equal(status.statusCode, 123) 149 | done() 150 | }) 151 | }) 152 | it('should return non-standard status cpde', function(done){ 153 | apistatus('http://localhost:9876/777', function(status){ 154 | assert.equal(status.category, "Non-standard Category") 155 | done() 156 | }) 157 | }) 158 | it('should return non-standard status description', function(done){ 159 | apistatus('http://localhost:9876/123', function(status){ 160 | assert.equal(status.message, "Non-standard Status") 161 | done() 162 | }) 163 | }) 164 | it('should return standard status descriptions', function(done){ 165 | apistatus('http://localhost:9876/500', function(status){ 166 | assert.equal(status.message, "Internal Server Error") 167 | done() 168 | }) 169 | }) 170 | }) 171 | 172 | describe('HAR Requests', function () { 173 | var har = require('./har.json') 174 | it('should be an array ', function(done){ 175 | apistatus(har, function(data){ 176 | assert.equal(data instanceof Array, true) 177 | done() 178 | }) 179 | }) 180 | it('should have three results', function(done){ 181 | apistatus(har, function(data){ 182 | assert.equal(data.length, 3) 183 | done() 184 | }) 185 | }) 186 | it('should throw an error on invalid har', function(done){ 187 | assert.throws(function() { 188 | apistatus({"not": "a har file"}, function(){}) 189 | }, Error) 190 | done() 191 | }) 192 | it('should have have the correct status codes', function(done){ 193 | apistatus(har, function(data){ 194 | var codes = [200, 301, 501], c = 0 195 | data.map(function(d){ 196 | assert.equal(d.statusCode, codes[c++]) 197 | }) 198 | done() 199 | }) 200 | }) 201 | }) 202 | 203 | describe('Latency', function () { 204 | it('should be a number', function(){ 205 | apistatus('http://mockbin.com', function(status){ 206 | assert.equal(typeof status.latency, "number") 207 | done() 208 | }) 209 | }) 210 | }) 211 | --------------------------------------------------------------------------------