在前面的文章5.7 原生Deno实现简单HTTP服务 结尾中,可以看出简单用Deno
的原生TCP
服务直接处理HTTP
请求响应,在TCP
对话频繁建立过程中,导致TCP
对话读写时机混乱或者不对等,出现基准测试响应错误。
本章将基于 5.7 原生Deno实现简单HTTP服务 的原理,结合前面两章 5.8 原生Deno处理HTTP请求和5.9 原生Deno处理HTTP响应 所实现的能力,打造一个排队机制来控制HTTP
服务里的TCP
对话控制。
本来一开始没头绪的,但是后来参考了官方标准模块 deno/deno_std/http/server.ts 的实现。发现官方也是利用比较“巧妙”的方式来控制TCP
频繁对话控制,在此思想上做了调整,实现了一个比较健壮的HTTP
服务器。
- 初始化
TCP
服务 - 等待接收
TCP
对话 - 接收到
TCP
对话后,进行请求报文读取 - 如果没读取异常,就把对话存入队列,等待下一个对话读取和进入队列。
- 如果读取异常就捕获同时关闭对话
- 等待执行完对话队列的响应完毕后,就进入下一次
TCP
对话的接收
HTTP
上下文实现HTTP
服务实现
./demo/server/context.ts
import { Request, RequestReader } from "./../request/mod.ts";
import { Response, ResponseWriter } from "./../response/mod.ts";
/**
* @class Conn对话上下文
* 内置 HTTP请求操作
* 内置 HTTP响应操作
*/
class Context {
public req: Request;
public res: Response;
public conn: Deno.Conn;
constructor(conn: Deno.Conn) {
this.conn = conn;
this.req = new RequestReader(conn);
this.res = new ResponseWriter(conn);
}
/**
* Conn对话结束操作
*/
close() {
this.conn.close();
}
}
export { Context };
./demo/server/mod.ts
import { Context } from "./context.ts";
const listen = Deno.listen;
/**
* 等待延迟接口
*/
interface Deferred {
promise: Promise<any>;
resolve: (value?: unknown) => void;
reject: (value?: unknown) => void;
}
/**
* 初始化一个等待延时操作
* @return {Deferred}
*/
function deferred(): Deferred {
let resolve: (value?: unknown) => void = () => {};
let reject: (value?: unknown) => void = () => {};
const promise = new Promise((res, rej) => {
resolve = res;
reject = rej;
});
return {
promise,
resolve,
reject
};
}
/**
* HTTP上下文环境
*/
interface ContextEnv {
queue: Context[];
deferred: Deferred;
}
/**
* 处理HTTP上下文服务
* @param {ContextEnv} env 上下文环境
* @param {Conn} conn TCP对话
* @param {Context} ctx 一次TCP对话连接封装的HTTP上下文
*/
function serveContext(env: ContextEnv, conn: Deno.Conn, ctx?: Context) {
loopContext(conn).then(function([ctx, err]){
if (err) {
// 处理TCP对话如果有错误,就结束对话
// 一个HTTP 响应结束
conn.close();
return;
} else {
// 如果处理TCP对话没问题
// 就把TCP对话重新加入队列,重新下一次等待
if (ctx) {
env.queue.push(ctx);
env.deferred.resolve();
}
}
})
}
/**
* TCP 主服务方法
* @param addr
*/
async function* serve(opts: Deno.ListenOptions) {
// 监听 TCP 端口
const listener: Deno.Listener = Deno.listen(opts) as Deno.Listener;
// 初始化一个HTTP上下文环境
const env: ContextEnv = {
queue: [],
deferred: deferred()
};
// 等待接收TCP对话 方法
const acceptRoutine = () => {
// 操作TCP对话方法
const handleConn = (conn: Deno.Conn) => {
// 处理HTTP上下文服务
serveContext(env, conn);
// 安排TCP对话,加入TCP对话等待排队处理
scheduleAccept();
};
// TCP对话等待排队处理
const scheduleAccept = () => {
listener.accept().then(handleConn);
};
scheduleAccept();
};
// 等待接收TCP对话
acceptRoutine();
while (true) {
// 等待上一个HTTP上下文队列 全部清空执行完
await env.deferred.promise;
// 重新初始化一个等待延迟处理
env.deferred = deferred();
let queueToProcess = env.queue;
env.queue = [];
for (const ctx of queueToProcess) {
yield ctx;
// 处理下一个 HTTP上下文服务
serveContext(env, ctx.conn, ctx);
}
}
listener.close();
}
/**
* 创建 HTTP服务
* @param {string} addr
* @param {function} handler
*/
async function createHTTP(
opts: Deno.ListenOptions,
handler: (ctx: Context) => void
) {
const server = serve(opts);
for await (const ctx of server) {
// 处理每一个服务的操作
await handler(ctx);
}
}
/**
* 循环HTTP上下文的读取操作
* 等待取出问题,就是代表一个TCP对话已经结束
* @param {Conn} c
*/
async function loopContext(c: Deno.Conn): Promise<[Context|null, any]> {
const ctx = new Context(c);
let err: any;
try {
await ctx.req.getGeneral();
} catch (e) {
err = e;
}
if (err) {
return [null, err];
}
try {
await ctx.req.getHeaders();
} catch (e) {
err = e;
}
try {
await ctx.req.getBodyStream();
} catch (e) {
err = e;
}
return [ctx, err];
}
export class Server {
private _handler: (ctx: Context) => Promise<void> = (ctx: Context) => Promise.reject();
private _isInitialized: boolean = false; // 是否已经初始化
private _isListening: boolean = false; // 是否已经在监听中
createServer(handler: (ctx: Context) => Promise<void>) {
if (this._isInitialized !== true) {
this._handler = handler;
this._isInitialized = true;
return this;
} else {
throw new Error('The http service has been initialized');
}
}
listen(opts: Deno.ListenOptions, callback: Function) {
if (this._isListening !== true) {
const handler = this._handler;
createHTTP(opts, handler);
callback();
this._isInitialized = true;
} else {
throw new Error('The http service is already listening');
}
}
}
https://github.com/chenshenhai/deno_note/blob/master/demo/server/example.ts
./demo/server/example.ts
import { Server } from "./mod.ts";
const opts: Deno.ListenOptions = {
hostname: "127.0.0.1",
port: 3001
}
const server = new Server();
server.createServer(async ctx => {
ctx.res.setBody(`hello server!`);
ctx.res.setStatus(200);
await ctx.res.flush();
})
server.listen(opts, function() {
console.log('the server is starting');
})
在上上上一篇文章原生Deno实现简单HTTP服务 最后利用 autocannon
压了100个请求,导致TCP
对话出问题了。本篇实现的是稳定的HTTP
服务,就分 100 个请求、200 个请求分别验证一下。
- 安装测试工具
npm i -g autocannon