DENO本机HTTP服务器DeNo部署的中间件框架,Node.js 16.5及以后的Cloudflare工人和BUN。它还包括一个中间件路由器。
此中间件框架的灵感来自KOA和中间件路由器,灵感来自 @koa/路由器。
此读数重点关注橡木API的机制,适用于熟悉JavaScript中间件框架(如Express和Koa)以及对DeNo的体面理解的人。如果您不熟悉这些,请在oakserver.github.io/oak上查看文档。
另外,请查看我们的常见问题解答和社区资源的令人敬畏的网站。
笔记
此读数中的示例从main拉动,是为DeNo CLI或DeNo部署而设计的,当您想实际部署工作负载时,这可能是没有意义的。您可能需要将“ PIN”“ PIN”与特定版本兼容,该版本与您正在使用的DeNo版本兼容,并且具有您期望的固定API。 https://deno.land/x/支持在URL中使用git标签来指导您以特定版本为单位。因此,要使用Oak的13.0.0版本,您将要导入https://deno.land/x/[email protected]/mod.ts 。
橡木可在deno.land/x和JSR上使用。要使用deno.land/x ,导入到一个模块中:
import { Application } from "https://deno.land/x/oak/mod.ts" ;要从JSR使用,将导入到一个模块中:
import { Application } from "jsr:@oak/oak@14" ;橡木可用于NPM和JSR上的Node.js。要从NPM使用,请安装软件包:
npm i @oakserver/oak@14
然后导入一个模块:
import { Application } from "@oakserver/oak" ;要从JSR使用,请安装软件包:
npx jsr i @oak/oak@14
然后导入一个模块:
import { Application } from "@oak/oak/application" ; 笔记
当前不支持发送,Websocket升级和在TLS/HTTP上服务。
此外,Cloudflare Worker环境和执行上下文当前尚未暴露于中间件。
橡木可用于JSR的Cloudflare工人。要将包裹添加到您的CloudFlare Worker项目中:
npx jsr add @oak/oak@14
然后导入一个模块:
import { Application } from "@oak/oak/application" ;与其他运行时间不同,OAK应用程序不收听传入的请求,而是处理Worker提取请求。最小的示例服务器将是:
import { Application } from "@oak/oak/application" ;
const app = new Application ( ) ;
app . use ( ( ctx ) => {
ctx . response . body = "Hello CFW!" ;
} ) ;
export default { fetch : app . fetch } ; 笔记
当前不支持发送和Websocket升级。
橡木可用于JSR上的BUN。使用安装软件包:
bunx jsr i @oak/oak@14
然后导入一个模块:
import { Application } from "@oak/oak/application" ; 笔记
当前不支持发送和Websocket升级。
Application程序类协调管理HTTP服务器,运行中间件以及处理请求时发生的错误。通常使用两个方法: .use()和.listen() 。通过.use()方法添加了中间件,并且.listen()方法将启动服务器,并使用注册的中间件开始处理请求。
基本用法,对Hello World响应每个请求! :
import { Application } from "jsr:@oak/oak/application" ;
const app = new Application ( ) ;
app . use ( ( ctx ) => {
ctx . response . body = "Hello World!" ;
} ) ;
await app . listen ( { port : 8000 } ) ;然后,您将在DeNo中运行此脚本,例如:
> deno run --allow-net helloWorld.ts
有关DENO下运行代码的更多信息,或有关如何安装DENO CLI的信息,请查看DENO手册。
中间软件作为堆栈处理,每个中间件功能都可以控制响应的流程。当调用中间软件时,将其传递给堆栈中的“下一个”方法的上下文。
一个更复杂的例子:
import { Application } from "jsr:@oak/oak/application" ;
const app = new Application ( ) ;
// Logger
app . use ( async ( ctx , next ) => {
await next ( ) ;
const rt = ctx . response . headers . get ( "X-Response-Time" ) ;
console . log ( ` ${ ctx . request . method } ${ ctx . request . url } - ${ rt } ` ) ;
} ) ;
// Timing
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` ) ;
} ) ;
// Hello World!
app . use ( ( ctx ) => {
ctx . response . body = "Hello World!" ;
} ) ;
await app . listen ( { port : 8000 } ) ;要提供HTTPS服务器,然后app.listen()选项需要包括设置为true选项.secure选项并提供.certFile和.keyFile选项。
.handle()方法.handle()方法用于处理请求并接收响应,而无需让应用程序管理服务器方面。这是高级用法,大多数用户都希望使用.listen() 。
.handle()方法最多接受三个参数。第一个是Request参数,第二个是Deno.Conn参数。第三个可选参数是指示该请求是否是“安全”的标志,其源于与远程客户端的TLS连接。如果ctx.respond === true则使用Response对象或undefined方法解决该方法。
一个例子:
import { Application } from "jsr:@oak/oak/application" ;
const app = new Application ( ) ;
app . use ( ( ctx ) => {
ctx . response . body = "Hello World!" ;
} ) ;
const listener = Deno . listen ( { hostname : "localhost" , port : 8000 } ) ;
for await ( const conn of listener ) {
( async ( ) => {
const requests = Deno . serveHttp ( conn ) ;
for await ( const { request , respondWith } of requests ) {
const response = await app . handle ( request , conn ) ;
if ( response ) {
respondWith ( response ) ;
}
}
} ) ;
}应用程序实例也具有一些属性:
contextState - 确定用于创建新上下文状态的方法。 "clone"的值将把状态设置为应用程序状态的克隆。 "prototype"的值意味着该应用程序的状态将用作上下文状态的原型。 "alias"的值表示应用程序的状态和上下文状态将是对同一对象的引用。 "empty"的值将用一个空对象初始化上下文的状态。
.jsonBodyReplacer可选的替换函数,在形成响应时将应用于JSON体。
.jsonBodyReviver可选的reviver函数,在请求中读取JSON主体时将应用。
.keys
签名和验证cookie时要使用的键。该值可以设置为一个键和KeyStack的实例,或提供与KeyStack相同接口的对象(例如KeyGrip的实例)。如果仅通过键,OAK将通过KeyStack进行管理键,该键可以轻松旋转,而无需重新签名数据值。
.proxy
默认为false ,但可以通过Application构造函数选项设置。这旨在表明该应用程序是代理后面的,并且将在处理请求时使用X-Forwarded-Proto , X-Forwarded-Host和X-Forwarded-For ,这应该提供有关请求的更准确的信息。
.state
应用程序状态的记录,可以通过在构造Application()时指定通用参数或通过传递状态对象(例如Application({ state }) )来强烈键入。
传递给中间件的上下文具有多个属性:
.app
对正在调用此中间件的Application的引用。
.cookies
此上下文的Cookies实例,该实例使您可以阅读和设置cookie。
.request
包含有关请求的详细信息的Request对象。
.respond
确定中间件是否完成处理时,应用程序应将.response发送给客户端。如果是true ,则将发送响应,如果false则不会发送响应。默认值为true ,但是某些方法,例如.upgrade()和.sendEvents()会将其设置为false 。
.response
将用于形成发送给请求者的响应Response对象。
.socket
如果连接尚未升级到Web插座,则将undefined 。如果连接已升级,则将设置.socket接口。
.state
应用程序状态的记录,可以通过在构造Application()时指定通用参数或通过传递状态对象(例如Application({ state }) )来强烈键入。
传递给中间件的上下文有一些方法:
.assert()
提出一个断言,如果不是真的,就会抛出一个由第二个参数识别的子类的HTTPError ,而消息是第三个参数。
.send()
将文件流向请求客户端。有关更多信息,请参见下面的静态内容。
.sendEvents()
将当前连接转换为服务器量事件响应,然后返回ServerSentEventTarget ,可以将消息和事件流传输到客户端。这将.respond为false 。
.throw()
抛出HTTPError ,该子类由第一个参数识别,消息传递为第二个。
.upgrade()
尝试将连接升级到Web套接字连接,然后返回WebSocket接口。以前的Oak版本,这将是通过std/ws Web套接字解决的Promise 。
与其他中间件框架不同, context没有大量的别名。有关请求的信息仅位于.request中,有关响应的信息仅位于.response中。
context.cookies允许访问请求中的cookie值,并允许在响应中设置cookie。如果在应用程序上设置了.keys属性,它将自动确保cookie。因为.cookies使用Web Crypto API签名和验证cookie,并且这些API以异步方式工作,所以Cookie API以异步方式工作。它有几种方法:
.get(key: string, options?: CookieGetOptions): Promise<string | undefined>
尝试从请求中检索cookie并根据密钥返回cookie的值。如果设置了应用程序.keys ,则将使用cookie的cookie验证cookie。如果cookie有效,则承诺将以该值解决。如果它无效,则将在响应中删除cookie签名。如果cookie没有由当前密钥签署,则将辞职并添加到响应中。
.set(key: string, value: string, options?: CookieSetDeleteOptions): Promise<void>
将根据提供的密钥,值和任何选项在响应中设置cookie。如果设置了应用程序.keys ,则将签署cookie,并将签名添加到响应中。当键异步签名时,建议.set()方法。
context.request包含有关请求的信息。它包含几个属性:
.body
提供对请求主体的访问的对象。有关请求主体API的详细信息,请参见下文。
.hasBody
如果请求可能有一个主体,则设置为true ,或者如果不为false 。不过,它无法验证身体是否由内置的身体解析器支撑。
[!警告]这是一个不可靠的API。在许多情况下,在HTTP/2中,由于HTTP/2的流性质,除非您尝试阅读身体,否则您无法确定请求是否具有身体。从DENO 1.16.1开始,对于HTTP/1.1,DENO也反映了这种行为。确定请求是否具有身体的唯一可靠方法是尝试阅读身体。
最好通过给定方法确定身体是否对您有意义,然后尝试在给定的上下文中读取和处理身体。例如, GET和HEAD绝不能具有身体,但是诸如DELETE和OPTIONS类的方法可能具有身体,并且如果对您的应用有意义,则应进行有条件处理的身体。
.headers
请求的标题, Headers的实例。
.method
代表请求的HTTP方法的字符串。
.originalRequest
弃用这将在以后的Oak发行中删除。
“ RAW” NativeServer RESICT,这是DOM Request对象的抽象。 .originalRequest.request是正在处理的DOM Request实例。用户通常应避免使用这些。
.secure
一个适用于.protocol的快捷方式,如果https否则为false则返回true 。
.source
在DENO下运行时, .source将设置为源Web标准Request 。在nodejs下运行时,这将是undefined 。
.url
一个基于请求的完整URL的URL实例。这代替了在请求对象的其余部分中暴露于URL的一部分。
和几种方法:
.accepts(...types: string[])
协商响应请求支持的内容类型。如果没有传递内容类型,该方法将返回接受的内容类型的优先数组。如果传递了内容类型,则返回最佳协商内容类型。如果没有内容类型匹配undefined返回。
.acceptsEncodings(...encodings: string[])
协商响应请求支持的编码内容。如果没有传递编码,则该方法将返回优先的接受编码数组。如果传递编码,则返回最好的协商编码。如果没有编码匹配,则返回undefined 。
.acceptsLanguages(...languages: string[])
谈判客户能够理解的语言。该区域变体偏爱的地方。如果没有传递编码,则该方法将返回优先理解的语言数组。如果通过语言,则返回最好的谈判语言。如果没有语言匹配undefined语言,则将返回。
重要的
该API在橡木V13和更高版本中发生了很大变化。自橡木于2018年创建以来,以前的API已经有机地生长,并且没有代表任何其他常见的API。 V13中引入的API与Fetch API的Request方式更好地保持一致,并且对于第一次来橡树的开发人员应该更熟悉。
OAK请求的.body的灵感来自Fetch API的Request ,但有一些添加功能。上下文的request.body是一个提供多个属性的对象的实例:
.has
如果请求可能有一个主体,则设置为true ,或者如果不为false 。不过,它无法验证身体是否由内置的身体解析器支撑。
[!重要]这是一个不可靠的API。在许多情况下,在HTTP/2中,由于HTTP/2的流性质,除非您尝试阅读身体,否则您无法确定请求是否具有身体。从DENO 1.16.1开始,对于HTTP/1.1,DENO也反映了这种行为。确定请求是否具有身体的唯一可靠方法是尝试阅读身体。
最好通过给定方法确定身体是否对您有意义,然后尝试在给定的上下文中读取和处理身体。例如, GET和HEAD绝不能具有身体,但是诸如DELETE和OPTIONS类的方法可能具有身体,并且如果对您的应用有意义,则应进行有条件处理的身体。
.stream
ReadableStream<Uint8Array>它将允许在Uint8Array块中读取身体。这是Fetch API Request中的.body属性。
.used
如果使用身体,则设置为true ,否则将其设置为false 。
它也有几种方法:
arrayBuffer()
用一个包含身体内容(如果有)的ArrayBuffer来解决。适用于阅读/处理二进制数据。
blob()
用包含身体内容物的Blob解决。适用于阅读/处理二进制数据。
form()
用URLSearchParams解析,该parmes已从身体的内容中解码。这适用于具有内容类型的application/x-www-form-urlencoded身体。
formData()
用FormData实例解析,该实例已从身体的内容中解码。这适用于具有内容类型的multipart/form-data的身体。
json()
从人体中解析为JSON的数据解决。如果在应用程序中指定了jsonBodyReviver ,则在解析JSON时将使用它。
text()
用代表身体内容物的字符串解决。
type()
试图提供有关如何编码身体的指导,该指导可用于确定用于解码身体的方法。该方法返回一个表示解释的身体类型的字符串:
| 价值 | 描述 |
|---|---|
"binary" | 该身体具有指示二进制数据的内容类型,并且应使用.arrayBuffer() ,. .blob()或.stream应用于读取身体。 |
"form" | 主体被编码为表单数据,应使用.form()读取身体。 |
"form-data" | 身体被编码为多部分形式,应使用.formData()读取身体。 |
"json" | 将身体编码为JSON数据,并应使用.json()来读取身体。 |
"text" | 身体被编码为文本,并且应使用.text()来读取身体。 |
"unknown" | 要么没有身体,要么无法根据内容类型确定身体类型。 |
.type()方法还采用可选的自定义媒体类型的参数,这些媒体类型将在尝试确定身体类型时使用。然后将它们合并到默认媒体类型中。该值是一个对象,其中键是binary , form , form-data , json或text的对象之一,并且该值是与类型IS格式兼容的格式的适当媒体类型。
context.response包含有关响应的信息,这些信息将发送回请求者。它包含几个属性:
.body
响应的主体通常可以通过下面记录的自动响应主体来处理。
.headers
Headers实例,其中包含响应的标题。
.status
HTTP Status代码将带回响应。如果在响应之前未设置此设置,则橡木将默认为200 OK如果有一个.body ,则404 Not Found 。
.type
介质类型或扩展名为响应设置Content-Type标头。例如,您可以提供txt或text/plain原来描述身体。
和几种方法:
.redirect(url?: string | URL | REDIRECT_BACK, alt?: string | URL)
一种简化将响应重定向到另一个URL的方法。它将将Location标头设置为所提供的url ,并将状态302 Found (除非状态已经是3XX状态)。使用符号REDIRECT_BACK作为url表示请求中的Referer标头应作为方向,如果未设置Referer ,则alt为替代位置。如果既没有设置alt和Referer ,则重定向将发生到/ 。基本的HTML(如果请求者支持它)或将设置文本主体,以解释它们正在重定向。
.toDomResponse()
这将转换橡木的信息了解对提取API Response的响应。这将最终确定响应,从而进一步尝试修改投掷的响应。这旨在在橡木中内部使用,以便能够响应请求。
.with(response: Response) .with(body?: BodyInit, init?: ResponseInit)
这设置了对Web标准Response的响应。请注意,这将忽略/覆盖其他中间件设置的任何其他信息,包括要设置的标题或cookie之类的内容。
当响应Content-Type未设置在.response的标题中时,Oak将自动尝试确定适当的Content-Type 。首先,它将查看.response.type 。如果分配,它将尝试基于将.type作为媒体类型的值或基于扩展解决媒体类型解决适当的媒体类型。例如,如果.type设置为"html" ,则Content-Type将设置为"text/html" 。
如果未设置.type的值,则OAK将检查.response.body的值。如果值是string ,则橡木将检查字符串看起来是否像HTML,如果是的,则将设置为Content-Type text/html否则将其设置为text/plain 。如果值是一个对象,除了Uint8Array , Deno.Reader或null之外,该对象将传递给JSON.stringify() ,并且Content-Type将设置为application/json 。
如果身体类型是数字,bigint或符号,则将其胁迫到字符串并将其视为文本。
如果身体的值是一个函数,则该函数将在没有参数的情况下调用。如果该函数的返回值是有希望的,那将是等待的,并且解决值将如上所述处理。如果值不承诺,则将如上所述处理。
应用方法.listen()用于打开服务器,开始侦听请求以及为每个请求处理注册的中间件。当服务器关闭时,此方法将返回承诺。
服务器打开后,在开始处理请求之前,应用程序将启动"listen"事件,可以通过.addEventListener()方法来侦听。例如:
import { Application } from "jsr:@oak/oak/application" ;
const app = new Application ( ) ;
app . addEventListener ( "listen" , ( { hostname , port , secure } ) => {
console . log (
`Listening on: ${ secure ? "https://" : "http://" } ${
hostname ?? "localhost"
} : ${ port } ` ,
) ;
} ) ;
// register some middleware
await app . listen ( { port : 80 } ) ;如果要关闭应用程序,则该应用程序支持流产信号的选项。这是使用信号的示例:
import { Application } from "jsr:@oak/oak/application" ;
const app = new Application ( ) ;
const controller = new AbortController ( ) ;
const { signal } = controller ;
// Add some middleware using `app.use()`
const listenPromise = app . listen ( { port : 8000 , signal } ) ;
// In order to close the server...
controller . abort ( ) ;
// Listen will stop listening for requests and the promise will resolve...
await listenPromise ;
// and you can do something after the close to shutdown中间件可用于处理中间件的其他错误。等待其他中间件在捕获错误时执行。因此,如果您有一个错误处理中间件,可以对错误进行良好的响应,则可以这样奏效:
import { Application } from "jsr:@oak/oak/application" ;
import { isHttpError } from "jsr:@oak/commons/http_errors" ;
import { Status } from "jsr:@oak/commons/status" ;
const app = new Application ( ) ;
app . use ( async ( ctx , next ) => {
try {
await next ( ) ;
} catch ( err ) {
if ( isHttpError ( err ) ) {
switch ( err . status ) {
case Status . NotFound :
// handle NotFound
break ;
default :
// handle other statuses
}
} else {
// rethrow if you can't handle the error
throw err ;
}
}
} ) ;该应用程序将捕获未接收的中间件例外。 Application扩展了DENO中的全局EventTarget ,当中间软件或发送响应中未知错误时, EventError将被派发到该应用程序。要收听这些错误,您将在应用程序实例中添加一个事件处理程序:
import { Application } from "jsr:@oak/oak/application" ;
const app = new Application ( ) ;
app . addEventListener ( "error" , ( evt ) => {
// Will log the thrown error to the console.
console . log ( evt . error ) ;
} ) ;
app . use ( ( ctx ) => {
// Will throw a 500 on every request.
ctx . throw ( 500 ) ;
} ) ;
await app . listen ( { port : 80 } ) ; Router类生产中间件,可与Application一起使用,以根据请求的路径名启用路由。
下面的示例提供了一份书籍地图的恢复服务,其中http://localhost:8000/book/将返回一系列书籍, http://localhost:8000/book/1将带有ID返回书籍"1" :
import { Application } from "jsr:@oak/oak/application" ;
import { Router } from "jsr:@oak/oak/router" ;
const books = new Map < string , any > ( ) ;
books . set ( "1" , {
id : "1" ,
title : "The Hound of the Baskervilles" ,
author : "Conan Doyle, Arthur" ,
} ) ;
const router = new Router ( ) ;
router
. get ( "/" , ( context ) => {
context . response . body = "Hello world!" ;
} )
. get ( "/book" , ( context ) => {
context . response . body = Array . from ( books . values ( ) ) ;
} )
. get ( "/book/:id" , ( context ) => {
if ( books . has ( context ?. params ?. id ) ) {
context . response . body = books . get ( context . params . id ) ;
}
} ) ;
const app = new Application ( ) ;
app . use ( router . routes ( ) ) ;
app . use ( router . allowedMethods ( ) ) ;
await app . listen ( { port : 8000 } ) ;通过使用regexp的路径将通道转换为正则表达式,这意味着在模式中表达的参数将被转换。 path-to-regexp具有高级用法,可以创建可用于匹配的复杂模式。如果您有高级用例,请查看该库的文档。
在大多数情况下, context.params的类型。params是从路径模板字符串通过打字稿魔术自动推断出来的。但是,在更复杂的情况下,这可能不会产生正确的结果。在这种情况下,您可以使用router.get<RouteParams>覆盖该类型,其中RouteParams是context.params的显式类型。
支持嵌套路由器。以下示例响应http://localhost:8000/forums/oak/posts and http://localhost:8000/forums/oak/posts/nested-routers 。
import { Application } from "jsr:@oak/oak/application" ;
import { Router } from "jsr:@oak/oak/router" ;
const posts = new Router ( )
. get ( "/" , ( ctx ) => {
ctx . response . body = `Forum: ${ ctx . params . forumId } ` ;
} )
. get ( "/:postId" , ( ctx ) => {
ctx . response . body =
`Forum: ${ ctx . params . forumId } , Post: ${ ctx . params . postId } ` ;
} ) ;
const forums = new Router ( ) . use (
"/forums/:forumId/posts" ,
posts . routes ( ) ,
posts . allowedMethods ( ) ,
) ;
await new Application ( ) . use ( forums . routes ( ) ) . listen ( { port : 8000 } ) ; 该函数send()旨在作为中间件功能的一部分提供静态内容。在最直截了当的用法中,提供了一个根,并通过本地文件系统从请求的路径相对于root提供了向函数提供的请求。
基本用法看起来像这样:
import { Application } from "jsr:@oak/oak/application" ;
const app = new Application ( ) ;
app . use ( async ( context , next ) => {
try {
await context . send ( {
root : ` ${ Deno . cwd ( ) } /examples/static` ,
index : "index.html" ,
} ) ;
} catch {
await next ( ) ;
}
} ) ;
await app . listen ( { port : 8000 } ) ; send()自动支持诸如在响应中提供ETag和Last-Modified标头,以及请求中的If-None-Match If-Modified-Since标头等功能。这意味着,在提供静态内容时,客户将能够依靠其资产的缓存版本,而不是重新下载它们。
send()方法自动支持为静态资产生成ETag标头。标题允许客户端确定是否需要重新下载资产,但是计算其他方案的ETag s可能很有用。
有一个中间件功能来评估context.reponse.body ,并确定它是否可以为该体型创建ETag标头,如果这样,则将ETag标头设置为响应。基本用法看起来像这样:
import { Application } from "jsr:@oak/oak/application" ;
import { factory } from "jsr:@oak/oak/etag" ;
const app = new Application ( ) ;
app . use ( factory ( ) ) ;
// ... other middleware for the application还有一个函数,它基于将其读取到内存中逻辑的内容来检索一个实体,该函数可以传递给ETAG计算的ETAG,这是DENO STD库的一部分:
import { Application } from "jsr:@oak/oak/application" ;
import { getEntity } from "jsr:@oak/oak/etag" ;
import { calculate } from "jsr:@std/http/etag" ;
const app = new Application ( ) ;
// The context.response.body has already been set...
app . use ( async ( ctx ) => {
const entity = await getEntity ( ctx ) ;
if ( entity ) {
const etag = await calculate ( entity ) ;
}
} ) ; Deno.serve()迁移如果您是从Deno.serve()或为Web标准提取API Request和Response设计的调整代码的迁移,则有几个橡木功能可以提供帮助。
ctx.request.source在DENO下运行时,将设置为Fetch API Request ,直接访问原始请求。
ctx.response.with()此方法将接受提取API Response ,或根据提供的BodyInit和ResponseInit创建新响应。这也将最终确定响应,忽略橡木.response上可能设置的任何内容。
middleware/serve#serve()和middelware/serve#route()这两个中间件生成器可用于调整更像Deno.serve()操作的代码,因为它提供了Fetch API Request ,并期望处理程序通过Fetch API Response解决。
使用serve()与Application.prototype.use() :
import { Application } from "jsr:@oak/oak/application" ;
import { serve } from "jsr:@oak/oak/serve" ;
const app = new Application ( ) ;
app . use ( serve ( ( req , ctx ) => {
console . log ( req . url ) ;
return new Response ( "Hello world!" ) ;
} ) ) ;
app . listen ( ) ;并且类似的解决方案可与route()一起使用,其中上下文包含有关路由器的信息,例如参数:
import { Application } from "jsr:@oak/oak/application" ;
import { Router } from "jsr:@oak/oak/router" ;
import { route } from "jsr:@oak/oak/serve" ;
const app = new Application ;
const router = new Router ( ) ;
router . get ( "/books/:id" , route ( ( req , ctx ) => {
console . log ( ctx . params . id ) ;
return Response . json ( { title : "hello world" , id : ctx . params . id } ) ;
} ) ) ;
app . use ( router . routes ( ) ) ;
app . listen ( ) ; mod.ts导出一个名为testing的对象,其中包含一些用于测试您可能创建的橡木中间件的实用程序。有关更多信息,请参见使用Oak的测试。
有几个模块直接从其他模块进行调整。他们保留了个人许可和版权。所有模块,包括直接调整的模块均根据MIT许可获得许可。
所有其他工作都是版权2018-2024橡树作者。版权所有。