复制浏览器代码

master
yutent 2023-01-13 18:28:04 +08:00
parent 7d68bd92ff
commit daabe8861c
8 changed files with 455 additions and 4 deletions

17
.gitignore vendored Normal file
View File

@ -0,0 +1,17 @@
node_modules/
dist/
.vscode
*.sublime-project
*.sublime-workspace
package-lock.json
._*
.Spotlight-V100
.Trashes
.DS_Store
.AppleDouble
.LSOverride

10
.prettierrc.yaml Normal file
View File

@ -0,0 +1,10 @@
jsxBracketSameLine: true
jsxSingleQuote: true
semi: false
singleQuote: true
printWidth: 80
useTabs: false
tabWidth: 2
trailingComma: none
bracketSpacing: true
arrowParens: avoid

21
LICENSE Normal file
View File

@ -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.

83
Readme.md Normal file
View File

@ -0,0 +1,83 @@
## node-fetch的增强版
> node-fetch的增强版, 增加注入及数据处理, 支持多实例。
## Node.js 兼容性
因为需要支持 ESM。所以需要 Node.js >= v12.0.0,
### 示例
```js
import fetch from '@bytedo/node-fetch'
fetch('/get_list', {body: {page: 1}})
.then(r => r.json())
.then(list => {
console.log(list)
})
// 创建一个新的fetch实例, 可传入新的基础域名, 和公共参数等
var f1 = fetch.create('//192.168.1.101', {headers: {token: 123456}})
f1('/get_list', {body: {page: 1}})
.then(r => r.json())
.then(list => {
console.log(list)
})
```
### APIs
#### 1. fetch(url[, options<Object>])
> 发起一个网络请求, options的参数如下。 同时支持配置公共域名, 公共参数。
+ method`<String>` 默认GET, 可选GET/POST/PUT/DELETE...
+ body`<Any>` 要发送的数据, 如果是不允许有`body`的方式, 会被自动拼接到url上
+ cache`<String>` 是否缓存,
+ credentials`<String/Boolean>` 是否校验
+ signal`<Object>` 网络控制信号, 可用于中断请求
+ timeout`<Number>` 超时时间, 默认30秒, 单位毫秒
```js
fetch.BASE_URL = '//192.168.1.100'
// 1.2.0开始支持注入
fetch.inject.request(function(conf) {
// 无需返回值, 但需要注意这是引用类型,不要对带个conf赋值
conf.headers.token = 123456
})
// 响应注入, 需要有返回值
fetch.inject.response(function(res) {
return res.json()
})
```
#### 2. fetch.create()
> 创建一个新的fetch实例, 可以无限创建多个实例(用于同一个项目中有多组不同的接口)。
```js
var another = fetch.create()
another.BASE_URL = '//192.168.1.101'
// 新创建的实例, 也支持注入
another.inject.request(function(conf) {
conf.headers.token = 123456
})
another.inject.response(function(res) {
return res.json()
})
```

18
build.js Normal file
View File

@ -0,0 +1,18 @@
/**
* {build}
* @author yutent<yutent.io@gmail.com>
* @date 2021/08/09 11:59:41
*/
import Es from 'esbuild'
const mode = process.argv.slice(2).shift()
Es.build({
entryPoints: ['src/index.js'],
bundle: true,
watch: mode === 'dev' ? true : false,
minify: mode === 'dev' ? false : true,
format: 'esm',
outdir: 'dist'
})

View File

@ -1,10 +1,12 @@
{ {
"type": "module",
"name": "@bytedo/node-fetch", "name": "@bytedo/node-fetch",
"version": "1.0.0", "version": "0.0.1",
"description": "", "description": "node-fetch的增强版, 增加注入及数据处理",
"main": "index.js", "main": "dist/index.js",
"scripts": { "scripts": {
"test": "echo \"Error: no test specified\" && exit 1" "start": "node build.js dev",
"build": "node build.js"
}, },
"repository": { "repository": {
"type": "git", "type": "git",
@ -15,5 +17,8 @@
"fetch" "fetch"
], ],
"author": "", "author": "",
"dependencies": {
"node-fetch": "^3.3.0"
},
"license": "MIT" "license": "MIT"
} }

221
src/index.js Normal file
View File

@ -0,0 +1,221 @@
/**
* 新一代版本
* @author yutent<yutent.io@gmail.com>
* @date 2020/07/31 18:59:47
*/
import nativeFetch from 'node-fetch'
import { Format, toS } from './lib/format.js'
const NOBODY_METHODS = ['GET', 'HEAD']
const FORM_TYPES = {
form: 'application/x-www-form-urlencoded; charset=UTF-8',
json: 'application/json; charset=UTF-8',
text: 'text/plain; charset=UTF-8'
}
const ERRORS = {
10001: 'Argument url is required',
10012: 'Parse error',
10100: 'Request canceled',
10104: 'Request pending...',
10200: 'Ok',
10204: 'No content',
10304: 'Not modified',
10500: 'Internal Server Error',
10504: 'Connected timeout'
}
class _Request {
constructor(url = '', options = {}, owner) {
if (!url) {
throw new Error(ERRORS[10001])
}
// url规范化
url = url.replace(/#.*$/, '')
if (owner.BASE_URL) {
if (!/^([a-z]+:|\/\/)/.test(url)) {
url = owner.BASE_URL + url
}
}
options.method = (options.method || 'get').toUpperCase()
this._owner = owner
this.options = {
headers: {
'content-type': FORM_TYPES.form
},
body: null,
cache: 'default',
signal: null, // 超时信号, 配置该项时, timeout不再生效
timeout: 30000 // 超时时间, 单位毫秒, 默认30秒
}
if (!options.signal) {
this.control = new AbortController()
options.signal = this.control.signal
}
if (options.headers) {
let headers = this.options.headers
Object.assign(headers, options.headers)
options.headers = headers
}
Object.assign(this.options, options, { url })
if (owner._inject_req) {
owner._inject_req(this.options)
}
return this.__next__()
}
__next__() {
var options = this.options
var hasAttach = false // 是否有附件
var noBody = NOBODY_METHODS.includes(options.method)
/* -------------------------- 1»» 请求的内容 --------------------- */
if (options.body) {
var type = typeof options.body
switch (type) {
case 'number':
case 'string':
this.__type__('text')
break
case 'object':
// 如果是一个 FormData对象,且为不允许携带body的方法,则直接改为POST
if (options.body.constructor === FormData) {
hasAttach = true
// 修正请求类型
if (noBody) {
options.method = 'POST'
}
} else {
for (let k in options.body) {
if (toS.call(options.body[k]) === '[object File]') {
hasAttach = true
break
}
}
// 有附件,则改为FormData
if (hasAttach) {
if (noBody) {
options.method = 'POST'
}
options.body = Format.mkFormData(options.body)
}
}
break
}
}
if (hasAttach) {
delete options.headers['content-type']
}
/* -------------------------- 2»» 处理跨域 --------------------- */
/* ------------- 3»» 根据method类型, 处理表单数据 ---------------- */
// 拼接到url上
if (noBody) {
let tmp = Format.param(options.body)
if (tmp) {
options.url += (~options.url.indexOf('?') ? '&' : '?') + tmp
}
delete options.body
} else {
if (!hasAttach) {
if (~options.headers['content-type'].indexOf('json')) {
options.body = JSON.stringify(options.body)
} else {
options.body = Format.param(options.body)
}
}
}
/* ----------------- 4»» 超时处理 -----------------------*/
if (options.timeout && options.timeout > 0) {
this.timer = setTimeout(_ => {
this.abort()
}, options.timeout)
delete options.timeout
}
/* ----------------- 5»» 构造请求 ------------------- */
var url = options.url
delete options.url
for (let k in options) {
if (
options[k] === null ||
options[k] === undefined ||
options[k] === ''
) {
delete options[k]
}
}
return nativeFetch(url, options)
.then(r => {
clearTimeout(this.timer)
let isSucc = r.status >= 200 && r.status < 400
let _type
if (this._owner._inject_res) {
r = this._owner._inject_res(r)
_type = toS.call(r)
}
if (isSucc) {
return r
} else {
if (_type === '[object Promise]') {
return r.then(_ => Promise.reject(_))
} else {
return Promise.reject(r)
}
}
})
.catch(e => {
clearTimeout(this.timer)
return Promise.reject(e)
})
}
abort() {
this.control.abort()
}
__type__(type) {
this.options.headers['content-type'] = FORM_TYPES[type]
}
}
function inject(target) {
target.inject = {
request(callback) {
target._inject_req = callback
},
response(callback) {
target._inject_res = callback
}
}
}
const fetch = function (url, options) {
return new _Request(url, options, fetch)
}
fetch.create = function () {
var another = function (url, options) {
return new _Request(url, options, another)
}
inject(another)
return another
}
inject(fetch)
export default fetch

76
src/lib/format.js Normal file
View File

@ -0,0 +1,76 @@
/**
*
* @authors yutent (yutent.io@gmail.com)
* @date 2016-11-26 16:35:45
*
*/
export const toS = Object.prototype.toString
export const encode = encodeURIComponent
export const decode = decodeURIComponent
/**
* 表单序列化
*/
function serialize(p, obj, query) {
var k
if (Array.isArray(obj)) {
obj.forEach(function (it, i) {
k = p ? `${p}[${Array.isArray(it) ? i : ''}]` : i
if (typeof it === 'object') {
serialize(k, it, query)
} else {
query(k, it)
}
})
} else {
for (let i in obj) {
k = p ? `${p}[${i}]` : i
if (typeof obj[i] === 'object') {
serialize(k, obj[i], query)
} else {
query(k, obj[i])
}
}
}
}
export const Format = {
mkFormData(data) {
let form = new FormData()
for (let i in data) {
let el = data[i]
if (Array.isArray(el)) {
el.forEach(function (it) {
form.append(i + '[]', it)
})
} else {
form.append(i, data[i])
}
}
return form
},
param(obj) {
if (!obj || typeof obj === 'string' || typeof obj === 'number') {
return obj
}
let arr = []
let query = function (k, v) {
if (/native code/.test(v)) {
return
}
v = typeof v === 'function' ? v() : v
v = toS.call(v) === '[object File]' ? v : encode(v)
arr.push(encode(k) + '=' + v)
}
if (typeof obj === 'object') {
serialize('', obj, query)
}
return arr.join('&')
}
}