Skip to content

Latest commit

 

History

History
326 lines (258 loc) · 8.52 KB

http_stable.md

File metadata and controls

326 lines (258 loc) · 8.52 KB

原生Deno实现稳定HTTP服务

前言

在前面的文章5.7 原生Deno实现简单HTTP服务 结尾中,可以看出简单用Deno的原生TCP服务直接处理HTTP请求响应,在TCP对话频繁建立过程中,导致TCP对话读写时机混乱或者不对等,出现基准测试响应错误。

http-error

本章将基于 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 上下文实现

./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 };

HTTP 服务实现

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

发起100请求测试

  • autocannon http://127.0.0.1:3001/ -c 100
  • 就会出现以下结果 server_100_conn

发起200请求测试

  • autocannon http://127.0.0.1:3001/ -c 200
  • 就会出现以下结果 server_200

单元测试