JSX AS DSL? 写个 Mock API 服务器看看
这几天打算写一个简单的 API Mock 服务器,老生常谈哈?其实我是想讲 JSX, Mock 服务器只是一个幌子。
我在寻找一种更简洁、方便、同时又可以灵活扩展的、和别人不太一样的方式,来定义各种 Mock API。后来我发现了 JSX 在领域问题描述的优势和潜力,当然这可不是空谈,我们会实际写一个项目来证实这个判断。
文章大纲
1. 领域问题的描述
一上来就说这么抽象的名词,'领域问题' 是什么鬼?什么是领域,Wiki 上解释的非常好,领域就是指某一专业或事物方面范围的涵盖。那么所谓领域问题就可以理解为,我们需要通过程序或者其他方式去解决的需求。
比如提到 API Mock 服务器,我们需要解决的就是请求匹配和数据模拟这些问题;Nginx 解决的资源伺服和代理问题;HTML + CSS 解决的是页面 UI 展示问题...
我们这里重点关注'描述'。这些描述是提供给领域专家的‘前端‘ 或者 用户界面(UI)。举个例子:
描述的形式有很多,例如配置文件、编程语言、图形界面。 先来看看现在常见的工具是怎么做的:
1.1 配置文件形式
JSON?
JSON 是一种非常简单的数据表述, 没有任何学习成本,解析也非常方便。但是它有非常多致命的缺陷,比如不支持注释、冗余、数据结构单一。
YAML?
相比 JSON 语法要简洁很多、可读性也比较强。作为一个配置文件形式非常优秀
还是其他配置文件形式...
通常这些配置文件都是语言无关的,因此不会包含特定语言的元素。换句话说配置文件形式数据是相对静态的, 所以灵活性、扩展性比较差。只适合简单的配置场景。
举个例子,这些配置文件不支持函数。我们的 Mock 服务器可能需要通过一个函数来动态处理请求,所以配置文件在这里并不适用。
当然你可以通过其他方式来取代‘函数’,例如模板、或者脚本支持
1.2 编程语言与内部 DSL
我们需要回到编程语言本身,利用它的编程能力,实现配置文件无法实现的更强大的功能。
不过单纯使用通用类型编程语言,命令式的过程描述可能过于繁琐。我们最好针对具体领域问题进行简化和抽象,给用户提供一个友好的用户界面,让他们声明式地描述他们的领域问题。我们要尽可能减少用户对底层细节的依赖,与此同时最好能保持灵活的扩展能力。
我说的可能就是DSL(Domain-specific languages):
DSL 是一种用于描述特定应用领域的计算机语言。DSL 在计算机领域有非常广泛的应用,例如描述 Web 页面的 HTML、数据库查询语言 SQL、正则表达式。
相对应的是通用类型语言(GPL, General-Purpose Language),例如 Java、C++、JavaScript。它们可以用于描述任意的领域逻辑,它们通常是图灵完备的。
可以这么认为,虽然不严谨:除了通用类型语言、其他语言都算是 DSL。
怎么创建 DSL?
从头开发一门新语言?No! 成本太高了
一种更优雅的方式是在通用编程语言的基础上进行减法或者封装抽象。当然不是所有类型语言都有这个'能力', 比如 Java、C/C++ 就不行,它们的语法太 Verbose 或者工具链太重了。但是 Groovy、Ruby、Scala、还有 Elixir 这些语言就可以方便地创建出‘DSL’, 而且它们大部分是动态语言。
它们有的借助宏、有的天生语法就非常适合作为 DSL、有的具备非常强的动态编程能力... 这些因素促就了它们适合作为 DSL 的母体(宿主)。
我们通常也将这种 DSL 称为 Embedded DSL(嵌入式 DSL)
或者 内部 DSL
,因为它们寄生在通用类型编程语言中。而独立的 DSL,如 JSON、HTML,称为外部DSL
。
内部 DSL 好处是省去了实现一门语言的复杂性(Parse->Transform->Generate)。
举两个非常典型的例子:
Java 开发者常用的 Gradle,基于 Groovy:
plugins {
id 'java-library'
}
repositories {
jcenter()
}
dependencies {
api 'org.apache.commons:commons-math3:3.6.1'
implementation 'com.google.guava:guava:27.0.1-jre'
testImplementation 'junit:junit:4.12'
}
复制代码
还有 CocoaPods, 基于 Ruby:
source 'http://source.git'
platform :ios, '8.0'
target 'Demo' do
pod 'AFNetworking'
pod 'SDWebImage'
pod 'Masonry'
pod "Typeset"
pod 'BlocksKit'
pod 'Mantle'
pod 'IQKeyboardManager'
pod 'IQDropDownTextField'
end
复制代码
具体的实现细节不在本文的范围之内,还是聊回 JavaScript。
我个人要求 DSL 应该具备这些特性:
- 专注于特定领域。也就是说它的目的非常明确,因此比通用类型语言要简单很多,但是它的边界有时候并不好把握。
- 组织性。它应该方便组织和描述领域问题, 或者说这是一种约束能力。配置文件组织性就非常好,比如 JSON,它可以很容易地描述数据结构,没有什么心智负担。另一个典型的例子是单元测试框架(例如 jest),它们使用 describe、it、expect 这些元件,让单元测试更好的组织起来。
- 可读性。它必须是人类可读的、容易理解的。
- 声明式。声明式优于过程式、描述 What 而不是 How。
- 扩展性。很多 DSL 一开始并不关注这一点,因为一开始问题可能并不复杂。问题的领域不是静态不变的,它可能会变大,这时候 DSL 的扩展能力就很关键了。 就比如 HTML,随着前端开发越来越复杂,原有的元素和功能集合已经无法满足需求,所以衍生除了很多组件或者自定义元素方案。如果原本的 DSL 无法扩展,可以在这个基础之上再套一层 DSL,CSS vs SASS、HTML vs React 就是这样的例子。
2. JavaScript 内部 DSL
上节提到了 Groovy、Ruby ‘适合‘ 用作 DSL 母体,并不代表一定要用它们实现,这只是说明它们天生具备的一些语言特性让实现更加便捷,或者说外观更加简洁。
Google 一把 ‘JavaScript DSL‘ 匹配的有效资料很少。 如果你觉得困惑那就应该回到问题本身, 最重要的是解决领域问题,至于怎么组织和描述则是相对次要的。所以不要去纠结 JavaScript 适不适合。
那我们就针对 Mock Server 这个具体领域,聊一聊 JavaScript 内部 DSL 的典型组织方式:
2.1 对象形式
最简单的方式是直接基于对象或者数组进行声明,实现简单又保持组织性。例如 Umi Mock 还有 飞冰 Mock, 就是基于对象组织的:
export default {
// 支持值为 Object 和 Array
'GET /api/users': { users: [1, 2] },
// GET POST 可省略
'/api/users/1': { id: 1 },
// 支持自定义函数,API 参考 express@4
'POST /api/users/create': (req, res) => {
res.end('OK')
},
// 使用 mockjs 等三方库
'GET /api/tags': mockjs.mock({
'list|100': [{ name: '@city', 'value|1-100': 50, 'type|0-2': 1 }],
}),
}
复制代码
和配置文件差不多, 实现和使用都非常简单 ,简单的 API Mock 场景开箱即用,对于复杂的用法和 API 协议,也可以通过自定义函数进一步封装。但是有时候我们希望库可以承担多一点事情。
2.2 链式调用形式
JavaScript 作为内部 DSL 的另外一种典型的形式是链式调用。
其中最出名的是 JQuery, 它让链式调用这种模式广为人知。相比啰嗦的原生 DOM 操作代码,JQuery 确实让人眼前一亮, 它暴露精简的 API, 帮我们屏蔽了许多底层 DOM 操作细节,抚平平台差异,同时还能保持灵活性和扩展性。这才是它真正流行的原因,大众喜闻乐见的都是简单的东西。
$('.awesome')
.addClass('flash')
.draggable()
.css('color', 'red')
复制代码
JQuery 这种 API 模式也影响到了其他领域,比如 Iot 领域的 Ruff
:
$.ready(function(error) {
if (error) {
console.log(error)
return
}
// 点亮灯
$('#led-r').turnOn()
})
复制代码
jest
expect(z).not.toBeNull()
expect(z).toBeDefined()
expect(value).toBeGreaterThan(3)
expect(value).toBeGreaterThanOrEqual(3.5)
复制代码
API Mock 服务器领域也有两个这样的例子:
Nock:
const scope = nock('http://myapp.iriscouch.com')
.get('/users/1')
.reply(404)
.post('/users', {
username: 'pgte',
email: 'pedro.teixeira@gmail.com',
})
.reply(201, {
ok: true,
id: '123ABC',
rev: '946B7D1C',
})
.get('/users/123ABC')
.reply(200, {
_id: '123ABC',
_rev: '946B7D1C',
username: 'pgte',
email: 'pedro.teixeira@gmail.com',
})
复制代码
还有网易云团队的 Srvx
get('/handle(.*)').to.handle(ctx => {
ctx.body = 'handle'
})
get('/blog(.*)').to.json({ code: 200 })
get('/code(.*)').to.send('code', 201)
get('/json(.*)').to.send({ json: true })
get('/text(.*)').to.send('haha')
get('/html(.*)').to.send('<html>haha</html>')
get('/rewrite:path(.*)').to.rewrite('/query{path}')
get('/redirect:path(.*)').to.redirect('localhost:9002/proxy{path}')
get('/api(.*)').to.proxy('http://mock.server.com/')
get('/test(.*)').to.proxy('http://mock.server.com/', {
secure: false,
})
get('/test/:id').to.proxy('http://{id}.dynamic.server.com/')
get('/query(.*)').to.handle(ctx => {
ctx.body = ctx.query
})
get('/header(.*)')
.to.header({ 'X-From': 'svrx' })
.json({ user: 'svrx' })
get('/user').to.json({ user: 'svrx' })
get('/sendFile/:path(.*)').to.sendFile('./{path}')
复制代码
链式调用模式目前是主流的 JavaScript 内部 DSL 形式。而且实现也比较简单,更重要的是它接近自然语言。
2.3 ES2015 Template Tag
近年基于 ES6 Template Tag 特性引入‘新语言‘到 JavaScript 的库层出不穷。
不过因为 ES6 Template Tag 本质上是字符串,所以需要解析和转换,因此更像是外部 DSL。别忘了 Compiler as Framework! 通常我们可以利用 Babel 插件在编译时提前将它们转换为 JavaScript 代码。
举几个流行的例子:
Zebu: 这是一个专门用于解析 Template Tag 的小型编译器, 看看它的一些内置例子:
// 范围
range`1,3 ... (10)` // [1, 3, 5, 7, 9]
// 状态机, 牛逼
const traffic = machine`
initState: #green
states: #green | #yellow | #red
events: #timer
onTransition: ${state => console.log(state)}
#green @ #timer -> #yellow
#yellow @ #timer -> #red
#red @ #timer -> #green
`
traffic.start() // log { type: "green" }
traffic.send({ type: 'timer' }) // log { type: "yellow" }
复制代码
Jest 表格测试:
describe.each`
a | b | expected
${1} | ${1} | ${2}
${1} | ${2} | ${3}
${2} | ${1} | ${3}
`('$a + $b', ({ a, b, expected }) => {
test(`returns ${expected}`, () => {
expect(a + b).toBe(expected)
})
test(`returned value not be greater than ${expected}`, () => {
expect(a + b).not.toBeGreaterThan(expected)
})
test(`returned value not be less than ${expected}`, () => {
expect(a + b).not.toBeLessThan(expected)
})
})
复制代码
除此之外还有:
Template Tag 这些方案给我们开了很多脑洞。尽管如此,它也带来了一些复杂性,就像开头说的,它们是字符串,需要解析、语法检查和转换,且 JavaScript 本身的语言机制并没有给它们带来多少便利(如语法高亮、类型检查)。
2.4 要不试试 JSX?
铺垫了这么多,只是前戏。上面提到这些方案,要么过于简单、要么过于复杂、要么平淡无奇。我将目光投向了 JSX,我发现它可以满足我的大部分需求。
先来看看一下我们的 Mock 服务器的原型设计:
import { Get, Post, mock } from 'jsxmock'
export default (
<server port="4321">
{/* 首页 */}
<Get>hello world</Get>
{/* 登录 */}
<Post path="/login">login success</Post>
{/* 返回 JSON */}
<Get path="/json">{{ id: 1 }}</Get>
{/* mockjs */}
<Get path="/mockjs">{mock({ 'id|+1': 1, name: '@name' })}</Get>
{/*自定义逻辑*/}
<Get path="/user/:id">{(req, res) => res.send('hello')}</Get>
</server>
)
复制代码
嵌套匹配场景
export default (
<server>
<Get path="/api">
{/* 匹配 /api?method=foo */}
<MatchBySearch key="method" value="foo">
foo
</MatchBySearch>
{/* 匹配 /api?method=bar */}
<MatchBySearch key="method" value="bar">
bar
</MatchBySearch>
<BlackHole>我会吃掉任何请求</BlackHole>
</Get>
</server>
)
复制代码
有点 Verbose? 进一步封装组件:
const MyAwesomeAPI = props => {
const { path = '/api', children } = props
return (
<Get path={path}>
{Object.keys(children).map(name => (
<MatchBySearch key="method" value={name}>
{children[name]}
</MatchBySearch>
))}
</Get>
)
}
export default (
<server>
<MyAwesomeAPI>{{ foo: 'foo', bar: 'bar' }}</MyAwesomeAPI>
<MyAwesomeAPI path="/api-2">{{ hello: 'foo', world: 'bar' }}</MyAwesomeAPI>
</server>
)
复制代码
看起来不错哈?我们看到了 JSX 作为 DSL 的潜力,也把 React 的组件思维搬到了 GUI 之外的领域。
你知道我的风格,篇幅较长 ☕️ 休息一会,再往下看。
3. JSX 入门
如果你是 React 的开发者,JSX 应该再熟悉不过了。它不过是一个语法糖,但是它目前不是 JavaScript 标准的一部分。Babel、Typescript 都支持转译 JSX。
例如
const jsx = (
<div foo="bar">
<span>1</span>
<span>2</span>
<Custom>custom element</Custom>
</div>
)
复制代码
会转译为:
const jsx = React.createElement(
'div',
{
foo: 'bar',
},
React.createElement('span', null, '1'),
React.createElement('span', null, '2'),
React.createElement(Custom, null, 'custom element')
)
复制代码
3.1 自定义工厂
JSX 需要一个工厂方法来创建创建'节点实例'。默认是 React.createElement
。我们可以通过注释配置来提示转译插件。按照习惯,自定义工厂都命名为 h
:
/* @jsx h */
/* @jsxFrag 'fragment' */
import { h } from 'somelib'
const jsx = (
<div foo="bar">
<span>1</span>
<span>2</span>
<>fragement</>
</div>
)
复制代码
将转译为:
import { h } from 'somelib'
const jsx = h(
'div',
{
foo: 'bar',
},
h('span', null, '1'),
h('span', null, '2'),
h('fragment', null, 'fragement')
)
复制代码
3.2 Host Component vs Custom Component
JSX 会区分两种组件类型。小写开头的为内置组件,它们以字符串的形式传入 createElement; 大写开头的表示自定义组件, 作用域内必须存在该变量, 否则会报错。
// 内置组件
;<div />
// 自定义组件
;<Custom />
复制代码
3.3 简单实现 createElement 工厂方法
export function createElement(type, props, ...children) {
const copy = { ...(props || EMPTY_OBJECT) }
copy.children = copy.children || (children.length > 1 ? children : children[0])
return {
_vnode: true,
type,
props: copy,
}
}
复制代码
4. 基础组件的设计
4.1 来源于 Koa 的灵感
大家应该比较熟悉 koa 中间件机制。
// logger
app.use(async (ctx, next) => {
await next()
const rt = ctx.response.get('X-Response-Time')
console.log(`${ctx.method} ${ctx.url} - ${rt}`)
})
// x-response-time
app.use(async (ctx, next) => {
const start = Date.now()
await next()
const ms = Date.now() - start
ctx.set('X-Response-Time', `${ms}ms`)
})
// response
app.use(async ctx => {
ctx.body = 'Hello World'
})
复制代码
形象的说,它就是一个洋葱模型:
中间件调用 next,就会进入下一级。 如果把函数的边界打破。它的样子确实像洋葱:
✨我发现使用 JSX 可以更直观地表示这种洋葱结构
4.2 use 基础组件
于是乎,有了 <use />
这个基础组件。它类似于 Koa 的 app.use
, 用于拦截请求,可以进行响应, 也可以选择进入下一层。
① 来看看整体设计。
use 正是基于上面说的,使用 JSX 来描述中间件包裹层次的基础组件。因为使用的是一种树状结构,所以要区分兄弟中间件和子中间件:
<server>
<use m={A}>
<use m={Aa} />
<use m={Ab} />
</use>
<use m={B} />
<use m={C} />
</server>
复制代码
其中 Aa
、Ab
就是 A
的子中间件。在 A 中可以调用类似 koa 的 next
函数,进入下级中间件。
A
、B
、C
之间就是兄弟中间件。当前继中间件未匹配时,就会执行下一个相邻中间件。
乍一看,这就是 koa 和 express 的结合啊!
② 再看看 Props 设计
interface UseProps {
m: (req, res, recurse: () => Promise<boolean>) => Promise<boolean>;
skip?: boolean;
}
复制代码
m
req
、res
:Express 的请求对象和响应对象recurse
:递归执行子级中间件, 类似 koa 的 next。返回一个Promise<boolean>
, 它将在下级中间件执行完成后 resolve,boolean 表示下级中间件是否匹配拦截了请求。返回值:返回一个
Promise<boolean>
表示当前中间件是否匹配(拦截请求)。如果匹配,后续的兄弟中间件将不会被执行。
skip
:强制跳过,我们在开发时可能会临时跳过匹配请求,这个有点像单元测试中的 skip
③ 看一下运行实例
假设代码为:
const cb = name => () => {
console.log(name)
return false
}
export default (
<server>
<use
m={async (req, res, rec) => {
console.log('A')
if (req.path === '/user') await rec() // 如果匹配,则放行,让其递归进入内部
console.log('end A')
return false
}}
>
<use m={cb('A-1')}>如果父级匹配,则这里会被执行</use>
<use m={cb('A-2')}>...</use>
</use>
<use m={cb('B')} />
<use m={cb('C')} />
</server>
)
复制代码
如果请求的是 '/',那么打印的是 A -> end A -> B -> C
; 如果请求为 '/user', 那么打印的是 A -> A-1 -> A-2 -> end A -> B -> C
我们的基础组件和 Koa / Express 一样,核心保持非常小而简洁,当然它也比较低级,这样能够保证灵活性。
这个简单的基础组件设计就是整个框架的‘基石’。 如果你了解 Koa 和 Express,这里没有新的东西。只是换了一种表现方式。
4.3 高层组件的封装
Ok, 有了 use
这个基础原语, 我可以做很多有意思的事情,使用组件化的思维封装出更高级的 API。
① <Log>
:打日志
封装一个最简单的组件:
export const Log: Component = props => {
return (
<use
m={async (req, res, rec) => {
const start = Date.now()
// 进入下一级
const rtn = await rec()
console.log(
`${req.method} ${req.path}: ${Date.now() - start}ms`
)
return rtn
}}
>
{props.children}
</use>
)
}
复制代码
用法:
<server>
<Log>
<Get>hello world</Get>
<Post path="/login">login sucess</Post>
...
</Log>
</server>
复制代码
② <NotFound>
: 404
export const NotFound = props => {
const { children } = props
return (
<use
m={async (req, res, rec) => {
const found = await rec()
if (!found) {
// 下级未匹配
res.status(404)
res.send('Not Found')
}
return true
}}
>
{children}
</use>
)
}
复制代码
用法和 Log 一样。recurse
返回 false 时,表示下级没有匹配到请求。
③ <Catch>
: 异常处理
export const Catch: Component = props => {
return (
<use
m={async (req, res, rec) => {
try {
return await rec()
} catch (err) {
res.status(500)
res.send(err.message)
return true
}
}}
>
{props.children}
</use>
)
}
复制代码
用法和 Log 一样。捕获下级中间件的异常。
④ <Match>
: 请求匹配
Match 组件也是一个非常基础的组件,其他高层组件都是基于它来实现。它用于匹配请求,并作出响应。先来看看 Props 设计:
export type CustomResponder =
| MiddlewareMatcher
| MockType
| boolean
| string
| number
| object
| null
| undefined
export interface MatchProps {
match?: (req: Request, res: Response) => boolean // 请求匹配
headers?: StringRecord // 默认响应报头
code?: number | string // 默认响应码
// children 类型则比较复杂, 可以是原始类型、对象、Mock对象、自定义响应函数,以及下级中间件
children?: ComponentChildren | CustomResponder
}
复制代码
Match 组件主体:
export const Match = (props: MatchProps) => {
const { match, skip, children } = props
// 对 children 进行转换
let response = generateCustomResponder(children, props)
return (
<use
skip={skip}
m={async (req, res, rec) => {
// 检查是否匹配
if (match ? match(req, res) : true) {
if (response) {
return response(req, res, rec)
}
// 如果没有响应器,则将控制权交给下级组件
return rec()
}
return false
}}
>
{children}
</use>
)
}
复制代码
限于篇幅,Match 的具体细节可以看这里
前进,前进。 Get
、Post
、Delete
、MatchByJSON
、MatchBySearch
都是在 Match
基础上封装了,这里就不展开了。
⑤ <Delay>
: 延迟响应
太兴奋了,一不小心又写得老长,我可以去写小册了。Ok, 最后一个例子, 在 Mock API 会有模拟延迟响应的场景, 实现很简单:
export const Delay = (props: DelayProps) => {
const { timeout = 3000, ...other } = props
return (
<use
m={async (req, res, rec) => {
await new Promise(res => setTimeout(res, timeout))
return rec()
}}
>
<Match {...other} />
</use>
)
}
复制代码
用法:
<Get path="/delay">
{/* 延迟 5s 返回 */}
<Delay timeout={5000}>Delay Delay...</Delay>
</Get>
复制代码
更多使用案例,请看 jsxmock 文档)
坚持到这里不容易,你对它的原理可能感兴趣,那不妨继续看下去。
5. 浅谈实现原理
简单看一下实现。如果了解过 React 或者 Virtual-DOM 的实现原理。这一切就很好理解了。
5.1 '渲染'
这是打了引号的'渲染'。这只是一种习惯的称谓,并不是指它会渲染成 GUI。它用来展开整颗 JSX 树。对于我们来说很简单,我们没有所谓的更新或者 UI 渲染相关的东西。只需递归这个树、收集我们需要的东西即可。
我们的目的是收集到所有的中间件,以及它们的嵌套关系。我们用 MiddlewareNode 这个树形数据结构来存储它们:
export type Middleware = (
req: Request,
res: Response,
// 递归
recurse: () => Promise<boolean>,
) => Promise<boolean>
export interface MiddlewareNode {
m: Middleware // 中间件函数
skip: boolean // 是否跳过
children: MiddlewareNode[] // 子级中间件
}
复制代码
渲染函数:
let currentMiddlewareNode
export function render(vnode) {
// ...
//
JSX AS DSL? 写个 Mock API 服务器看看的更多相关文章
- Golang:使用 httprouter 构建 API 服务器
https://medium.com/@gauravsingharoy/build-your-first-api-server-with-httprouter-in-golang-732b7b01f6 ...
- ionic 运行过程中动态切换API服务器地址
ionic 运行过程中动态切换API服务器地址 keywords: ionic,phonegap,cordova,网络制式,动态切换,变更,API,服务器地址,$resource,localstora ...
- 《用Java写一个通用的服务器程序》01 综述
最近一两年用C++写了好几个基于TCP通信类型程序,都是写一个小型的服务器,监听请求,解析自定义的协议,处理请求,返回结果.每次写新程序时都把老代码拿来,修改一下协议解析部分和业务处理部分,然后就一个 ...
- 如何通过Mock API提高APP开发效率?
APP开发过程中,如果可以在客户端的正常项目代码中,自然地(不影响最终apk)添加一种模拟服务器数据返回的功能,这样就可以很方便的在不依赖服务器的情况下展开客户端的开发. Mock API提供了这一问 ...
- Mock API是如何在开发中发光发热的?
在长期的服务过程中,我们经常会遇到前来咨询的用户与我们反馈以下这种情况:咨询者是一个前端人员,在项目开发的过程中需要与后端进行对接,遇到后端还没完成数据输出的情况下,他只好写静态模拟数据,在遇到大型项 ...
- MOCK API 的定义及实践(使用eolinker实现)
MOCK API 的定义 根据百度百科的定义,mock测试就是在测试过程中,对于某些不容易构造或者不容易获取的对象,用一个虚拟的对象来创建以便测试的测试方法.这个虚拟的对象就是mock对象,mock对 ...
- Python+Flask搭建mock api server
Python+Flask搭建mock api server 前言: 近期由于工作需要,需要一个Mock Server调用接口直接返回API结果: 假如可以先通过接口文档的定义,自己模拟出服务器返回结果 ...
- 实现一个 RESTful API 服务器
RESTful 是目前最为流行的一种互联网软件结构.因为它结构清晰.符合标准.易于理解.扩展方便,所以正得到越来越多网站的采用. 什么是 REST REST(REpresentational Stat ...
- Vue.js(15)之 json-server搭建模拟的API服务器
json-server搭建模拟的API服务器 运行命令 npm install json-server -D 全局安装 json-server 项目根目录下创建 mock 文件夹 mock 文件夹下添 ...
随机推荐
- 洛谷P2664 树上游戏——点分治
原题链接 被点分治虐的心态爆炸了 题解 发现直接统计路径上的颜色数量很难,考虑转化一下统计方式.对于某一种颜色\(c\),它对一个点的贡献为从这个点出发且包含这种颜色的路径条数. 于是我们先点分一下, ...
- mysql_config_editor设置
[root@node01 etc]# mysql_config_editor set -G mysql3307 -S /tmp/mysql3307.sock -uroot -pEnter passwo ...
- 两种atm取款方式
1.//函数 密码 账号function User(username, password, account){ this.username = username; this.password = pa ...
- win10 LTSC 2019 激活
win 10 打开终端 1.slmgr -ipk M7XTQ-FN8P6-TTKYV-9D4CC-J462D 2.slmgr -skms kms.03k.org 3.slmgr -ato 4. slm ...
- hive优化实战
2019年1月8日,付哥给了我一份公司以前的一份SQL优化方案文档.十分感谢.记录了许多在公司以前优化的案例. -------------------------------------------- ...
- PHP解决h5页面跨域
前端h5 页面请求后端接口会出现跨域, PHP 只需三行代码即可解决 //解决前端跨域(h5页面) header("Access-Control-Allow-Origin:*"); ...
- Comet OJ - Contest #11 D isaster 重构树+倍增+dfs序+线段树
发现对于任意一条边,起决定性作用的是节点编号更大的点. 于是,对于每一条边,按照节点编号较大值作为边权,按照最小生成树的方式插入即可. 最后用线段树维护 dfs 序做一个区间查询即可. Code: # ...
- 路由器配置——RIP路由
一.实验目的:用rip路由实现全网互通 二.拓扑图: 三.具体步骤配置 (1)R1路由器配置 Router>enable --进入特权模式Router#configure terminal ...
- 数据结构实验之链表五:单链表的拆分(SDUT 2120)
#include <bits/stdc++.h> using namespace std; struct node { int data; struct node *next; }; st ...
- python正则表达式的用法
import re r1 = re.compile(r'(?im)(?P<name></html>)$') content = """ <H ...