Skip to content

Commit

Permalink
init
Browse files Browse the repository at this point in the history
  • Loading branch information
ChasLui committed Apr 10, 2019
0 parents commit 9a36198
Show file tree
Hide file tree
Showing 9 changed files with 266 additions and 0 deletions.
41 changes: 41 additions & 0 deletions application.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { serve } from './deps.ts';
import { Context } from './context.ts';
import { Middleware, compose } from './middleware.ts';

export class Application<S extends object = { [key: string]: any }> {
/**
* 注册的中间件
*
* @private
* @type {Middleware[]}
* @memberof Application
*/
private _middleware: Middleware[] = []
/**
* 将状态传递给前端视图的对象。 这可以通过在创建新应用程序时提供通用状态参数来键入。
*
* @example
* const app = new Application<{foo: string}>();
* @type {S}
* @memberof Application
*/
state: S;

async listen(addr: string): Promise<void> {
const middleware = compose(this._middleware);
const server = serve(addr);
for await(const req of server) {
const context = new Context(this, req);
await middleware(context);
await req.respond(context.response.toServerResponse());
}
}
/**
* 注册中间件
* @param middleware
*/
use(middleware: Middleware): this {
this._middleware.push(middleware);
return this;
}
}
41 changes: 41 additions & 0 deletions context.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { Application } from './application.ts';
import { ServerRequest } from './deps.ts';
import { Response } from './response.ts';

export class Context<S extends object = { [key: string]: any }> {
/**
* 当期应用的引用
*
* @type {Application<any>}
* @memberof Context
*/
app: Application<any>;
/**
* 请求对象
*
* @type {*}
* @memberof Context
*/
request: any;
/**
* 响应对象
*
* @type {*}
* @memberof Context
*/
response = new Response();
/**
* 将状态传递给前端视图的对象。 这可以通过在创建新应用程序时提供通用状态参数来键入。
*
* @example
* const app = new Application<{foo: string}>();
* @type {S}
* @memberof Context
*/
state: S;
constructor(app: Application<S>, serverRequest: ServerRequest) {
this.app = app;
this.state = app.state;
this.request = serverRequest;
}
}
4 changes: 4 additions & 0 deletions deps.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export { serve, ServerRequest, Response } from 'https://deno.land/[email protected]/http/server.ts';
export { Status, STATUS_TEXT } from 'https://deno.land/[email protected]/http/http_status.ts';
export { contentType } from 'https://deno.land/[email protected]/media_types/mod.ts';

36 changes: 36 additions & 0 deletions exapmles/server.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import {
green,
cyan,
bold,
yellow
} from 'https://deno.land/x/[email protected]/colors/mod.ts';

import { Application } from '../mod.ts';
(async() => {
const app = new Application();

// 注册中间件
app.use(async (ctx, next) => {
await next();
const rt = ctx.response.headers.get('X-Response-Time');
console.log(
`${green(ctx.request.method)} ${cyan(ctx.request.url)} - ${bold(
String(rt)
)}`
);
});
app.use(async (ctx, next) => {
const start = Date.now();
await next();
const ms = Date.now() - start;
ctx.response.headers.set('X-Response-Time', `${ms}ms`);
});
app.use(ctx => {
ctx.response.body = 'Hellow World!';
});

const address = '0.0.0.0:8000';
console.log(bold('Start listening on ') + yellow(address));
await app.listen(address);
console.log(bold('Finished.'))
})();
36 changes: 36 additions & 0 deletions middleware.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { Context } from './context.ts';

export interface Middleware {
(context: Context, next: () => Promise<void>): Promise<void> | void
}

/**
* 将多个中间件功能合成成一个中间件功能
* @param middleware
*/
export function compose(
middleware: Middleware[]
): (context: Context) => Promise<void> {
return function (context: Context, next?: () => Promise<void>) {
let index = -1;
/**
* 调度
* @param i
*/
async function dispatch(i: number) {
if (i <= index) {
throw new Error('next() called multiple times');
}
index = i;
let fn: Middleware | undefined = middleware[i];
if (i === middleware.length) {
fn = next;
}
if (!fn) {
return;
}
return fn(context, dispatch.bind(null, i + 1));
}
return dispatch(0);
}
}
2 changes: 2 additions & 0 deletions mod.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export { Application } from './application.ts'
export { Context } from './context.ts'
Empty file added request.ts
Empty file.
99 changes: 99 additions & 0 deletions response.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
import { contentType, Status } from './deps.ts';
import { isHtml } from './util.ts';
/**
* 服务响应对象
*/
interface ServerResponse {
status?: number,
headers?: Headers;
body?:Uint8Array;
}

/**
* body 类型
*/
const BODY_TYPES = ['string', 'number', 'bigint', 'boolean', 'symbol'];

/**
* body 编码对象
*/
const encoder = new TextEncoder();

export class Response {
/**
* 响应对象 body
*
* @type {*}
* @memberof Response
*/
body?: any;
/**
* 响应头
*/
headers = new Headers();
/**
* 响应状态码
*
* @type {Status}
* @memberof Response
*/
status?: Status;
/**
* 响应对象 mime 类型
*
* @type {string}
* @memberof Response
*/
type: string;

toServerResponse(): ServerResponse {
const body = this._getBody();
this._setContentType();
// 如果没有正文且没有内容类型且没有设置长度,则设置内容长度为0
if(!(
body ||
this.headers.has('Content-Type') ||
this.headers.has('Content-Length')
)) {
this.headers.append('Content-Length', '0');
}
return {
status: this.status || (body ? Status.OK : Status.NotFound),
body,
headers: this.headers
}
}
/**
* 获取响应对象
* @private
* @memberof Response
*/
private _getBody(): Uint8Array | undefined {
const typeofBody = typeof this.body;
let result: Uint8Array | undefined;
if (BODY_TYPES.includes(typeofBody)) {
const bodyText = String(this.body);
result = encoder.encode(bodyText);
this.type = this.type || isHtml(bodyText) ? "html" : "text/plain";
} else if(this.body instanceof Uint8Array) {
result = this.body;
} else if(typeofBody === 'object' && this.body !== null) {
result = encoder.encode(JSON.stringify(this.body));
this.type = this.type || 'json';
}
return result;
}
/**
* 设置响应对象内容类型字段
* @private
* @memberof Response
*/
private _setContentType() {
if(this.type) {
const contentTypeString = contentType(this.type);
if(contentTypeString && !this.headers.has("Content-Type")) {
this.headers.append("Content-Type", contentTypeString);
}
}
}
}
7 changes: 7 additions & 0 deletions util.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
/**
* 判断是否为 html
* @param value
*/
export function isHtml(value: string): boolean {
return /^\s*<(?:!DOCTYPE|html|body)/i.test(value);
}

0 comments on commit 9a36198

Please sign in to comment.