commit 7fdcf9562eaa525a2be2b30b5d57c415f85c0f63 Author: 宇天 Date: Wed Sep 16 16:01:46 2020 +0800 1.0.0 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..7c24ca8 --- /dev/null +++ b/.gitignore @@ -0,0 +1,9 @@ + +.Spotlight-V100 +.Trashes +.DS_Store +.AppleDouble +.LSOverride +._* +.idea +.vscode diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..ab60297 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2018 + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/Readme.md b/Readme.md new file mode 100644 index 0000000..81b8b6e --- /dev/null +++ b/Readme.md @@ -0,0 +1,185 @@ +![module info](https://nodei.co/npm/@gm5/response.png?downloads=true&downloadRank=true&stars=true) + +# @gm5/response + +> `@gm5/response` 对Http的response进一步封装, 提供常用的API. + +## 安装 + +```bash +npm install @gm5/response +``` + +## 使用 + +```javascript +import Response from '@gm5/response' +import http from 'http' + +http + .createServer((req, res) => { + let response = new Response(req, res) + + // it eq. argument res + console.log(response.res) + + response.set('content-type', 'text/html; charset=utf-8') + response.end('hello world') + }) + .listen(3000) +``` + +## API + + +### origin +> 返回原始的 request & response 对象. + + + +### error(msg[, code]) + +* msg `` +* code `` Http状态码 [可选] + +> 在客户端(浏览器)上输出友好的错误信息格式 + +```javascript +response.error('This is the error code', 500) // +response.error(null, 500) // null/empty, it will call the statusText back +response.error('Page not Found', 404) // +response.error(new Error('Auth denied'), 401) // +``` + +### status(code) + +* code `` + +> 设置Http状态码 + +```javascript +response.setStatus(501) // +response.setStatus(200) // +``` + +### set(key[, val]) + +* key `` | `` +* code `` | `` + +> 设置响应头, 属性字段名不区分大小写 + +**相同的字段会被覆盖.** +**`content-type`如果没有设置编码时, 会自动设置为utf8** + +```javascript +response.set('content-type', 'text/html; charset=utf-8') // +response.set('content-type', 'text/html') // 等价于上面的 + +response.set({'content-type', 'text/html', foo: 'bar'[, ...]}) +``` + +### append(key, val) + +* key `` +* code `` | `` + +> 设置响应头, 属性字段名不区分大小写。与`set()`的区别时, 这个不会覆盖相同的字段, 而是合并输出。 + +```javascript +response.append('name', 'foo') +response.append('name', 'bar') //客户端能同时看到foo和bar这2个值 +``` + +### get(key) + +* key `` + +> 获取即将要发送到客户端的头信息。 + +```javascript +response.set('name', 'foo') +response.get('name') // foo +``` + + +### redirect(url[, f]) + +* url `` +* f `` 是否永久重定向, 默认否 + +> 重定向url. + +```javascript +response.redirect('http://test.com/foo') +response.redirect('http://test.cn', true) +``` + +### location(url) + +* url `` + +> 重定向url. 但这是使用前端的方式跳转的. + +```javascript +response.location('http://test.com/foo') +response.location('/foo') +``` + +### render(data[, code]) + +* data `` | `` +* code `` Http状态码, 默认200 + +> 以html形式渲染内容。每次请求只能调用1次。 + + +```javascript +let html = fs.readFileSync('./index.html') +response.render(html) // send from a html file. + +let txt = '

hello doJS

' +response.render(txt) + +response.render("You're not able to here", 401) +``` + +### sendfile(data, filename) + +* data `` | `` +* filename `` + +> 直接以附件形式响应, 作为文件下载功能. + +```javascript +let pic = fs.readFileSync('./boy.jpg') +response.sendfile(pic, 'a-little-boy.jpg') // +``` + + + +### send(code[, msg][, data][, callback]) + +* code `` http状态码 +* msg `` 错误信息文本 +* data `` 响应主体内容, 可以是任意格式 +* callback `` 以jsonp形式返回对应的callback名 + +> 向客户端输出一个json(p), 支持resful api。 + + +```javascript +response.send(200, 'ok', { foo: 'bar' }) +// client will get the content like +// '{"code": 200, "msg": "ok", "data": {"foo": "bar"}}' + +response.send(200, 'success', { name: 'foo', age: 16 }, 'blabla') +// client will get the content like +// 'blabla({"code": 200, "msg": "success", "data": {"name": "foo", "age": 16}})' +``` + +### end([data]) + +* data `` | `` optional + +> 向客户端输出内容。 diff --git a/index.js b/index.js new file mode 100644 index 0000000..145e027 --- /dev/null +++ b/index.js @@ -0,0 +1,202 @@ +/** + * {description of this file} + * @author yutent + * @date 2020/09/16 14:52:58 + */ + +import STATUS_TEXT from './lib/http-code-msg.json' + +const CHARSET_REGEXP = /;\s*charset\s*=/ + +export default class Response { + constructor(req, res) { + this.origin = { req, res } + this.rendered = false + } + + /** + * [error http 错误显示] + * @param {Number} code [http错误码] + * @param {String} msg [错误提示信息] + */ + error(msg, code = 500) { + if (this.rendered) { + return + } + msg = msg || STATUS_TEXT[code] + + this.status(code) + this.set('Content-Type', 'text/html; charset=utf-8') + this.end( + `
Http Status: ${code}
${msg}
` + ) + } + + status(code = 404) { + this.statusCode = code + } + + /** + * [append 往header插入信息] + * @param {String} key [description] + * @param {String} val [description] + */ + append(key, val) { + if (this.rendered) { + return + } + let prev = this.get(key) + let value = val + + if (prev) { + if (Array.isArray(prev)) { + value = prev.concat(val) + } else if (Array.isArray(val)) { + value = [prev].concat(val) + } else { + value = [prev, val] + } + } + return this.set(key, value) + } + + /** + * [redirect 页面跳转] + * @param {String} url [要跳转的URL] + * @param {Boolean} f [是否永久重定向] + */ + redirect(url, f = false) { + if (this.rendered) { + return + } + if (!/^(http[s]?|ftp):\/\//.test(url)) { + url = '//' + url + } + this.set('Location', url) + this.status(f ? 301 : 302) + this.end('') + } + + /** + * [location 页面跳转(前端的方式)] + */ + location(url) { + var html = `` + if (this.rendered) { + return + } + this.render(html) + } + + // 以html格式向前端输出内容 + render(data, code) { + if (this.rendered) { + return + } + data += '' + data = data || STATUS_TEXT[code] + this.set('Content-Type', 'text/html') + this.set('Content-Length', Buffer.byteLength(data)) + if (code) { + this.status(code) + } + this.end(data) + } + + // 文件下载 + sendfile(data, filename) { + if (this.rendered) { + return + } + this.set('Content-Type', 'application/force-download') + this.set('Accept-Ranges', 'bytes') + this.set('Content-Length', Buffer.byteLength(data)) + this.set('Content-Disposition', `attachment;filename="${filename}"`) + this.end(data) + } + + /** + * [send json格式输出] + * @param {Num} code [返回码] + * @param {Str} msg [提示信息] + * @param {Str/Obj} data [额外数据] + * @param {Str} callback [回调函数名] + */ + send(code = 200, msg = 'success', data = null, callback = null) { + var output + + if (this.rendered) { + return + } + if (typeof code !== 'number') { + msg = code + '' + code = 400 + } else if (typeof msg === 'object') { + data = msg + code = code || 200 + msg = STATUS_TEXT[code] || 'success' + } + + output = { code, msg, data } + output = JSON.stringify(output) + + if (callback) { + callback = callback.replace(/[^\w\-\.]/g, '') + output = callback + '(' + output + ')' + } + + this.set('Content-Type', 'application/json') + this.set('Content-Length', Buffer.byteLength(output)) + + // 只设置200以上的值 + if (code && code > 200) { + this.status(code) + } + + this.end(output) + } + + end(buf) { + var code = 200 + if (this.rendered) { + return this + } + if (this.statusCode) { + code = this.statusCode + delete this.statusCode + } + this.rendered = true + this.origin.res.writeHead(code, STATUS_TEXT[code]) + this.origin.res.end(buf || '') + } + + /** + * [get 读取已写入的头信息] + */ + get(key) { + return this.origin.res.getHeader(key) + } + + /** + * [set 设置头信息] + */ + set(key, val) { + if (this.rendered) { + return this + } + if (arguments.length === 2) { + let value = Array.isArray(val) ? val.map(String) : String(val) + + if (key.toLowerCase() === 'content-type' && !CHARSET_REGEXP.test(value)) { + value += '; charset=utf-8' + } + + this.origin.res.setHeader(key, value) + } else { + for (let i in key) { + this.set(i, key[i]) + } + } + return this + } +} diff --git a/lib/http-code-msg.json b/lib/http-code-msg.json new file mode 100644 index 0000000..aece7af --- /dev/null +++ b/lib/http-code-msg.json @@ -0,0 +1,68 @@ +{ + "100": "Continue", + "101": "Switching Protocols", + "102": "Processing", + "200": "OK", + "201": "Created", + "202": "Accepted", + "203": "Non-Authoritative Information", + "204": "No Content", + "205": "Reset Content", + "206": "Partial Conten", + "207": "Multi-Status", + "208": "Already Reported", + "226": "IM Used", + "300": "Multiple Choices", + "301": "Moved Permanently", + "302": "Found", + "303": "See Other", + "304": "Not Modified", + "305": "Use Proxy", + "306": "Switch Proxy", + "307": "Temporary Redirect", + "308": "Permanent Redirect", + "400": "Bad Request", + "401": "Unauthorized", + "402": "Payment Required", + "403": "Forbidden", + "404": "Not Found", + "405": "Method Not Allowed", + "406": "Not Acceptable", + "407": "Proxy Authentication Required", + "408": "Request Timeout", + "409": "Conflict", + "410": "Gone", + "411": "Length Required", + "412": "Precondition Failed", + "413": "Request Entity Too Large", + "414": "Request-URI Too Long", + "415": "Unsupported Media Type", + "416": "Requested Range Not Satisfiable", + "417": "Expectation Failed", + "418": "I'm a teapot", + "420": "Enhance Your Caim", + "421": "Misdirected Request", + "422": "Unprocessable Entity", + "423": "Locked", + "424": "Failed Dependency", + "425": "Unordered Collection", + "426": "Upgrade Required", + "428": "Precondition Required", + "429": "Too Many Requests", + "431": "Request Header Fields Too Large", + "444": "No Response", + "450": "Blocked by Windows Parental Controls", + "451": "Unavailable For Legal Reasons", + "494": "Request Header Too Large", + "500": "Internal Server Error", + "501": "Not Implemented", + "502": "Bad Gateway", + "503": "Service Unavailable", + "504": "Gateway Timeout", + "505": "HTTP Version Not Supported", + "506": "Variant Also Negotiates", + "507": "Insufficient Storage", + "508": "Loop Detected", + "510": "Not Extended", + "511": "Network Authentication Required" +} \ No newline at end of file diff --git a/package.json b/package.json new file mode 100644 index 0000000..5e079d6 --- /dev/null +++ b/package.json @@ -0,0 +1,11 @@ +{ + "name": "@gm5/response", + "version": "1.0.0", + "type": "module", + "description": "对Http的response进一步封装, 提供常用的API", + "main": "index.js", + "author": "yutent", + "keywords": ["fivejs", "response", "http"], + "repository": "https://github.com/bytedo/gm5.response.git", + "license": "MIT" +}