GithubHelp home page GithubHelp logo

oakserver / oak Goto Github PK

View Code? Open in Web Editor NEW
5.0K 40.0 228.0 52.53 MB

A middleware framework for handling HTTP with Deno, Node, Bun and Cloudflare Workers 🐿️ 🦕

Home Page: https://oakserver.github.io/oak/

License: MIT License

TypeScript 99.84% HTML 0.02% Dockerfile 0.14%
middleware-framework deno http-server oak router-middleware middleware-frameworks

oak's Introduction

oak

jsr.io/@oak/oak jsr.io/@oak/oak score deno.land/x/oak npm Version

oak ci codecov

A middleware framework for Deno's native HTTP server, Deno Deploy, Node.js 16.5 and later, Cloudflare Workers and Bun. It also includes a middleware router.

This middleware framework is inspired by Koa and middleware router inspired by @koa/router.

This README focuses on the mechanics of the oak APIs and is intended for those who are familiar with JavaScript middleware frameworks like Express and Koa as well as a decent understanding of Deno. If you aren't familiar with these, please check out documentation on oakserver.github.io/oak.

Also, check out our FAQs and the awesome-oak site of community resources.

Note

The examples in this README pull from main and are designed for Deno CLI or Deno Deploy, which may not make sense to do when you are looking to actually deploy a workload. You would want to "pin" to a particular version which is compatible with the version of Deno you are using and has a fixed set of APIs you would expect. https://deno.land/x/ supports using git tags in the URL to direct you at a particular version. So to use version 13.0.0 of oak, you would want to import https://deno.land/x/[email protected]/mod.ts.

Usage

Deno CLI and Deno Deploy

oak is available on both deno.land/x and JSR. To use from deno.land/x, import into a module:

import { Application } from "https://deno.land/x/oak/mod.ts";

To use from JSR, import into a module:

import { Application } from "jsr:@oak/oak@14";

Node.js

oak is available for Node.js on both npm and JSR. To use from npm, install the package:

npm i @oakserver/oak@14

And then import into a module:

import { Application } from "@oakserver/oak";

To use from JSR, install the package:

npx jsr i @oak/oak@14

And then import into a module:

import { Application } from "@oak/oak/application";

Note

Send, websocket upgrades and serving over TLS/HTTPS are not currently supported.

In addition the Cloudflare Worker environment and execution context are not currently exposed to middleware.

Cloudflare Workers

oak is available for Cloudflare Workers on JSR. To use add the package to your Cloudflare Worker project:

npx jsr add @oak/oak@14

And then import into a module:

import { Application } from "@oak/oak/application";

Unlike other runtimes, the oak application doesn't listen for incoming requests, instead it handles worker fetch requests. A minimal example server would be:

import { Application } from "@oak/oak/application";

const app = new Application();

app.use((ctx) => {
  ctx.response.body = "Hello CFW!";
});

export default { fetch: app.fetch };

Note

Send and websocket upgrades are not currently supported.

Bun

oak is available for Bun on JSR. To use install the package:

bunx jsr i @oak/oak@14

And then import into a module:

import { Application } from "@oak/oak/application";

Note

Send and websocket upgrades are not currently supported.

Application, middleware, and context

The Application class coordinates managing the HTTP server, running middleware, and handling errors that occur when processing requests. Two of the methods are generally used: .use() and .listen(). Middleware is added via the .use() method and the .listen() method will start the server and start processing requests with the registered middleware.

A basic usage, responding to every request with 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 });

You would then run this script in Deno like:

> deno run --allow-net helloWorld.ts

For more information on running code under Deno, or information on how to install the Deno CLI, check out the Deno manual.

The middleware is processed as a stack, where each middleware function can control the flow of the response. When the middleware is called, it is passed a context and reference to the "next" method in the stack.

A more complex example:

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 });

To provide an HTTPS server, then the app.listen() options need to include the options .secure option set to true and supply a .certFile and a .keyFile options as well.

.handle() method

The .handle() method is used to process requests and receive responses without having the application manage the server aspect. This though is advanced usage and most users will want to use .listen().

The .handle() method accepts up to three arguments. The first being a Request argument, and the second being a Deno.Conn argument. The third optional argument is a flag to indicate if the request was "secure" in the sense it originated from a TLS connection to the remote client. The method resolved with a Response object or undefined if the ctx.respond === true.

An example:

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);
      }
    }
  });
}

An instance of application has some properties as well:

  • contextState - Determines the method used to create state for a new context. A value of "clone" will set the state as a clone of the app state. A value of "prototype" means the app's state will be used as the prototype of the context's state. A value of "alias" means that the application's state and the context's state will be a reference to the same object. A value of "empty" will initialize the context's state with an empty object.

  • .jsonBodyReplacer - An optional replacer function which will be applied to JSON bodies when forming a response.

  • .jsonBodyReviver - An optional reviver function which will be applied when reading JSON bodies in a request.

  • .keys

    Keys to be used when signing and verifying cookies. The value can be set to an array of keys, and instance of KeyStack, or an object which provides the same interface as KeyStack (e.g. an instance of keygrip). If just the keys are passed, oak will manage the keys via KeyStack which allows easy key rotation without requiring re-signing of data values.

  • .proxy

    This defaults to false, but can be set via the Application constructor options. This is intended to indicate the application is behind a proxy and will use X-Forwarded-Proto, X-Forwarded-Host, and X-Forwarded-For when processing the request, which should provide more accurate information about the request.

  • .state

    A record of application state, which can be strongly typed by specifying a generic argument when constructing an Application(), or inferred by passing a state object (e.g. Application({ state })).

Context

The context passed to middleware has several properties:

  • .app

    A reference to the Application that is invoking this middleware.

  • .cookies

    The Cookies instance for this context which allows you to read and set cookies.

  • .request

    The Request object which contains details about the request.

  • .respond

    Determines if when middleware finishes processing, the application should send the .response to the client. If true the response will be sent, and if false the response will not be sent. The default is true but certain methods, like .upgrade() and .sendEvents() will set this to false.

  • .response

    The Response object which will be used to form the response sent back to the requestor.

  • .socket

    This will be undefined if the connection has not been upgraded to a web socket. If the connection has been upgraded, the .socket interface will be set.

  • .state

    A record of application state, which can be strongly typed by specifying a generic argument when constructing an Application(), or inferred by passing a state object (e.g. Application({ state })).

The context passed to middleware has some methods:

  • .assert()

    Makes an assertion, which if not true, throws an HTTPError, which subclass is identified by the second argument, with the message being the third argument.

  • .send()

    Stream a file to the requesting client. See Static content below for more information.

  • .sendEvents()

    Convert the current connection into a server-sent event response and return a ServerSentEventTarget where messages and events can be streamed to the client. This will set .respond to false.

  • .throw()

    Throws an HTTPError, which subclass is identified by the first argument, with the message being passed as the second.

  • .upgrade()

    Attempt to upgrade the connection to a web socket connection, and return a WebSocket interface. Previous version of oak, this would be a Promise resolving with a std/ws web socket.

Unlike other middleware frameworks, context does not have a significant amount of aliases. The information about the request is only located in .request and the information about the response is only located in .response.

Cookies

The context.cookies allows access to the values of cookies in the request, and allows cookies to be set in the response. It automatically secures cookies if the .keys property is set on the application. Because .cookies uses the web crypto APIs to sign and validate cookies, and those APIs work in an asynchronous way, the cookie APIs work in an asynchronous way. It has several methods:

  • .get(key: string, options?: CookieGetOptions): Promise<string | undefined>

    Attempts to retrieve the cookie out of the request and returns the value of the cookie based on the key. If the applications .keys is set, then the cookie will be verified against a signed version of the cookie. If the cookie is valid, the promise will resolve with the value. If it is invalid, the cookie signature will be set to deleted on the response. If the cookie was not signed by the current key, it will be resigned and added to the response.

  • .set(key: string, value: string, options?: CookieSetDeleteOptions): Promise<void>

    Will set a cookie in the response based on the provided key, value and any options. If the applications .keys is set, then the cookie will be signed and the signature added to the response. As the keys are signed asynchronously, awaiting the .set() method is advised.

Request

The context.request contains information about the request. It contains several properties:

  • .body

    An object which provides access to the body of the request. See below for details about the request body API.

  • .hasBody

    Set to true if the request might have a body, or false if it does not. It does not validate if the body is supported by the built in body parser though.

    [!WARNING] This is an unreliable API. In HTTP/2 in many situations you cannot determine if a request has a body or not unless you attempt to read the body, due to the streaming nature of HTTP/2. As of Deno 1.16.1, for HTTP/1.1, Deno also reflects that behavior. The only reliable way to determine if a request has a body or not is to attempt to read the body.

    It is best to determine if a body might be meaningful to you with a given method, and then attempt to read and process the body if it is meaningful in a given context. For example GET and HEAD should never have a body, but methods like DELETE and OPTIONS might have a body and should be have their body conditionally processed if it is meaningful to your application.

  • .headers

    The headers for the request, an instance of Headers.

  • .method

    A string that represents the HTTP method for the request.

  • .originalRequest

    DEPRECATED this will be removed in a future release of oak.

    The "raw" NativeServer request, which is an abstraction over the DOM Request object. .originalRequest.request is the DOM Request instance that is being processed. Users should generally avoid using these.

  • .secure

    A shortcut for .protocol, returning true if HTTPS otherwise false.

  • .source

    When running under Deno, .source will be set to the source web standard Request. When running under NodeJS, this will be undefined.

  • .url

    An instance of URL which is based on the full URL for the request. This is in place of having parts of the URL exposed on the rest of the request object.

And several methods:

  • .accepts(...types: string[])

    Negotiates the content type supported by the request for the response. If no content types are passed, the method returns a prioritized array of accepted content types. If content types are passed, the best negotiated content type is returned. If no content type match undefined is returned.

  • .acceptsEncodings(...encodings: string[])

    Negotiates the content encoding supported by the request for the response. If no encodings are passed, the method returns a prioritized array of accepted encodings. If encodings are passed, the best negotiated encoding is returned. If no encodings match undefined is returned.

  • .acceptsLanguages(...languages: string[])

    Negotiates the language the client is able to understand. Where a locale variant takes preference. If no encodings are passed, the method returns a prioritized array of understood languages. If languages are passed, the best negotiated language is returned. If no languages match undefined is returned.

Request Body

Important

This API changed significantly in oak v13 and later. The previous API had grown organically since oak was created in 2018 and didn't represent any other common API. The API introduced in v13 aligns better to the Fetch API's Request way of dealing with the body, and should be more familiar to developers coming to oak for the first time.

The API for the oak request .body is inspired by the Fetch API's Request but with some add functionality. The context's request.body is an instance of an object which provides several properties:

  • .has

    Set to true if the request might have a body, or false if it does not. It does not validate if the body is supported by the built in body parser though.

    [!IMPORTANT] This is an unreliable API. In HTTP/2 in many situations you cannot determine if a request has a body or not unless you attempt to read the body, due to the streaming nature of HTTP/2. As of Deno 1.16.1, for HTTP/1.1, Deno also reflects that behavior. The only reliable way to determine if a request has a body or not is to attempt to read the body.

    It is best to determine if a body might be meaningful to you with a given method, and then attempt to read and process the body if it is meaningful in a given context. For example GET and HEAD should never have a body, but methods like DELETE and OPTIONS might have a body and should be have their body conditionally processed if it is meaningful to your application.

  • .stream

    A ReadableStream<Uint8Array> that will allow reading of the body in Uint8Array chunks. This is akin the .body property in a Fetch API Request.

  • .used

    Set to true if the body has been used, otherwise set to false.

It also has several methods:

  • arrayBuffer()

    Resolves with an ArrayBuffer that contains the contents of the body, if any. Suitable for reading/handling binary data.

  • blob()

    Resolves with a Blob that contains the contents of the body. Suitable for reading/handling binary data.

  • form()

    Resolves with a URLSearchParams which has been decoded from the contents of a body. This is appropriate for a body with a content type of application/x-www-form-urlencoded.

  • formData()

    Resolves with a FormData instance which has been decoded from the contents of a body. This is appropriate for a body with a content type of multipart/form-data.

  • json()

    Resolves with the data from the body parsed as JSON. If a jsonBodyReviver has been specified in the application, it will be used when parsing the JSON.

  • text()

    Resolves with a string that represents the contents of the body.

  • type()

    Attempts to provide guidance of how the body is encoded which can be used to determine what method to use to decode the body. The method returns a string that represents the interpreted body type:

    Value Description
    "binary" The body has a content type that indicates binary data and the .arrayBuffer(), .blob() or .stream should be used to read the body.
    "form" The body is encoded as form data and .form() should be used to read the body.
    "form-data" The body is encoded as a multi-part form and .formData() should be used to read the body.
    "json" The body is encoded as JSON data and .json() should be used to read the body.
    "text" The body is encoded as text and .text() should be used to read the body.
    "unknown" Either there is no body or it was not possible to determine the body type based on the content type.

    The .type() method also takes an optional argument of custom media types that will be used when attempting to determine the type of the body. These are then incorporated into the default media types. The value is an object where the key is one of binary, form, form-data, json, or text and the value is the appropriate media type in a format compatible with the type-is format.

Response

The context.response contains information about the response which will be sent back to the requestor. It contains several properties:

  • .body

    The body of the response, which can often be handled by the automatic response body handling documented below.

  • .headers

    A Headers instance which contains the headers for the response.

  • .status

    An HTTP Status code that will be sent back with the response. If this is not set before responding, oak will default to 200 OK if there is a .body, otherwise 404 Not Found.

  • .type

    A media type or extension to set the Content-Type header for the response. For example, you can provide txt or text/plain to describe the body.

And several methods:

  • .redirect(url?: string | URL | REDIRECT_BACK, alt?: string | URL)

    A method to simplify redirecting the response to another URL. It will set the Location header to the supplied url and the status to 302 Found (unless the status is already a 3XX status). The use of symbol REDIRECT_BACK as the url indicates that the Referer header in the request should be used as the direction, with the alt being the alternative location if the Referer is not set. If neither the alt nor the Referer are set, the redirect will occur to /. A basic HTML (if the requestor supports it) or a text body will be set explaining they are being redirected.

  • .toDomResponse()

    This converts the information oak understands about the response to the Fetch API Response. This will finalize the response, resulting in any further attempt to modify the response to throw. This is intended to be used internally within oak to be able to respond to requests.

  • .with(response: Response) and .with(body?: BodyInit, init?: ResponseInit)

    This sets the response to a web standard Response. Note that this will ignore/override any other information set on the response by other middleware including things like headers or cookies to be set.

Automatic response body handling

When the response Content-Type is not set in the headers of the .response, oak will automatically try to determine the appropriate Content-Type. First it will look at .response.type. If assigned, it will try to resolve the appropriate media type based on treating the value of .type as either the media type, or resolving the media type based on an extension. For example if .type was set to "html", then the Content-Type will be set to "text/html".

If .type is not set with a value, then oak will inspect the value of .response.body. If the value is a string, then oak will check to see if the string looks like HTML, if so, Content-Type will be set to text/html otherwise it will be set to text/plain. If the value is an object, other than a Uint8Array, a Deno.Reader, or null, the object will be passed to JSON.stringify() and the Content-Type will be set to application/json.

If the type of body is a number, bigint or symbol, it will be coerced to a string and treated as text.

If the value of body is a function, the function will be called with no arguments. If the return value of the function is promise like, that will be await, and the resolved value will be processed as above. If the value is not promise like, it will be processed as above.

Opening the server

The application method .listen() is used to open the server, start listening for requests, and processing the registered middleware for each request. This method returns a promise when the server closes.

Once the server is open, before it starts processing requests, the application will fire a "listen" event, which can be listened for via the .addEventListener() method. For example:

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 });

Closing the server

If you want to close the application, the application supports the option of an abort signal. Here is an example of using the signal:

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

Error handling

Middleware can be used to handle other errors with middleware. Awaiting other middleware to execute while trapping errors works. So if you had an error handling middleware that provides a well managed response to errors would work like this:

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;
    }
  }
});

Uncaught middleware exceptions will be caught by the application. Application extends the global EventTarget in Deno, and when uncaught errors occur in the middleware or sending of responses, an EventError will be dispatched to the application. To listen for these errors, you would add an event handler to the application instance:

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

The Router class produces middleware which can be used with an Application to enable routing based on the pathname of the request.

Basic usage

The following example serves up a RESTful service of a map of books, where http://localhost:8000/book/ will return an array of books and http://localhost:8000/book/1 would return the book with 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 });

A route passed is converted to a regular expression using path-to-regexp, which means parameters expressed in the pattern will be converted. path-to-regexp has advanced usage which can create complex patterns which can be used for matching. Check out the documentation for that library if you have advanced use cases.

In most cases, the type of context.params is automatically inferred from the path template string through typescript magic. In more complex scenarios this might not yield the correct result however. In that case you can override the type with router.get<RouteParams>, where RouteParams is the explicit type for context.params.

Nested routers

Nesting routers is supported. The following example responds to 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 });

Static content

The function send() is designed to serve static content as part of a middleware function. In the most straight forward usage, a root is provided and requests provided to the function are fulfilled with files from the local file system relative to the root from the requested path.

A basic usage would look something like this:

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() automatically supports features like providing ETag and Last-Modified headers in the response as well as processing If-None-Match and If-Modified-Since headers in the request. This means when serving up static content, clients will be able to rely upon their cached versions of assets instead of re-downloading them.

ETag support

The send() method automatically supports generating an ETag header for static assets. The header allows the client to determine if it needs to re-download an asset or not, but it can be useful to calculate ETags for other scenarios.

There is a middleware function that assesses the context.reponse.body and determines if it can create an ETag header for that body type, and if so sets the ETag header on the response. Basic usage would look something like this:

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

There is also a function which retrieves an entity for a given context based on what it logical to read into memory which can be passed to the etag calculate that is part of the Deno std library:

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);
  }
});

Fetch API and Deno.serve() migration

If you are migrating from Deno.serve() or adapting code that is designed for the web standard Fetch API Request and Response, there are a couple features of oak to assist.

ctx.request.source

When running under Deno, this will be set to a Fetch API Request, giving direct access to the original request.

ctx.response.with()

This method will accept a Fetch API Response or create a new response based on the provided BodyInit and ResponseInit. This will also finalize the response and ignores anything that may have been set on the oak .response.

middleware/serve#serve() and middelware/serve#route()

These two middleware generators can be used to adapt code that operates more like the Deno.serve() in that it provides a Fetch API Request and expects the handler to resolve with a Fetch API Response.

An example of using serve() with 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();

And a similar solution works with route() where the context contains the information about the router, like the params:

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();

Testing

The mod.ts exports an object named testing which contains some utilities for testing oak middleware you might create. See the Testing with oak for more information.


There are several modules that are directly adapted from other modules. They have preserved their individual licenses and copyrights. All of the modules, including those directly adapted are licensed under the MIT License.

All additional work is copyright 2018 - 2024 the oak authors. All rights reserved.

oak's People

Contributors

artisonian avatar bastidood avatar bodograumann avatar cmorten avatar derzade avatar esquevin avatar fonsp avatar hirasawayuki avatar justjavac avatar kidonng avatar kitsonk avatar kotlinisland avatar lideming avatar lucacasonato avatar manyuanrong avatar maxnest0x0 avatar mikelspohn avatar mikeybyker avatar mikolaj6r avatar n10000k avatar nielsbom avatar petruki avatar reed-jones avatar rokoucha avatar sillyfreak avatar smack0007 avatar soplwang avatar tootallnate avatar wongjiahau avatar zlumyo avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

oak's Issues

value is coming as Uint8Array

I am trying to make post request for login and, sending body with postman. But request.body().value returns a Uint8Array, because of this, i can't handle username and passoword.

Here is how i sent the data by using postman
Ekran Alıntısı3

Here is my code
Ekran Alıntısı
Ekran Alıntısı1

Is there any API documentation available?

Hi team,
great work! I am getting started with oak these days and looking for some materials, like API documentation. Is there any official API documentation available?
All the best,
Florian

ErrorEvent is not defined

Hello, I'm trying to use error handler, however I'm not able to use it

Can anyone help?

import { Application } from "https://deno.land/x/oak/mod.ts";

const app = new Application();

app.use((ctx) => {
  ctx.throw(500);// Uncaught ReferenceError: ErrorEvent is not defined
});

app.addEventListener("error", (evt) => {
  console.log(evt.error);
});

await app.listen({ port: 8000 });

Stdout:
image

Stopping the server

First and foremost: keep up the good work, i like where this project is going (:

I tried to implement an fs.watch logic that restarts oak on file change. For this I added the AbortController as mentioned in the README and I added controller.abort() to my fs.watch. Unfortunately neither the promise from app.listen was ever resolved, nor was the underlying server closed - it also seems like server.close() is not called in https://github.com/oakserver/oak/blob/master/application.ts#L226

Do I misunderstand the idea behind the AbortController? Or is this just not fully implemented?

Unpin deno_std dependencies

I'm not suggesting to disregard the deno_std version altogether, but ass of right now - without #19 -oak is incompatible with the latest version of Deno because all deps are pinned to v0.9.0. Obviously there are major benefits to having releases pinned to the Deno runtime version it was developed for, but it would be nice to at least have a version of oak without the dependencies pinned to a specific Deno version.

This could be in the form of the master branch or a latest/unpinned tagged release that keeps getting updated.

The second post JSON will result in 404 errors

I started a server with the following code.

import { Application, Router } from "https://deno.land/x/oak/mod.ts";

async function server() {
  const app = new Application();
  const router = new Router();
  router.post("/json", async ctx => {
    ctx.response.body = "test";
  });
  app.use(router.routes());
  await app.listen(`0.0.0.0:5000`);
}
server();

But when I submit requests usingPOST and application/json. The first time I get the right response, however, all subsequent requests will get 404 errors

However, when I use curl access, it seems normal. It only happens on the browser and postman. I guess it's related to the reuse of connections.

Using ctx.throw() inside router doesn't work as expected

version: 4.0.0

The issue is, the error is expected to be caught, and it is when throwing from app.use(), but when called from the router, the error makes the server crash.

const router = new Router();

app.use(async (ctx, next) => {
   try {
      await next();
   } catch(err) {
      console.log(err); // <-- this is not called
   }
})

// I throw an error here and expect it to be caught, or sent to the client, but the server crashes instead
router.get(ctx => ctx.throw(500));

app.use(router.routes());
app.use(router.allowedMethods());

await app.listen({ port: 8000 });

Pass through Reader response body type to Deno's HTTP server

Deno's current Response interface allows Reader as the body type.

/**
 * Interface of HTTP server response.
 * If body is a Reader, response would be chunked.
 * If body is a string, it would be UTF-8 encoded by default.
 */
export interface Response {
  status?: number;
  headers?: Headers;
  body?: Uint8Array | Reader | string;
}

This would be convenient to have in oak since, for example, dejs renders its templates to a Reader.

Property 'ErrorKind' does not exist on type 'typeof Deno'.

The object Deno changed. please use Deno.errors.NotFound instead of Deno.NotFound

error TS2339: Property 'ErrorKind' does not exist on type 'typeof Deno'.

► https://deno.land/x/oak/send.ts:149:29

149       if (err.kind === Deno.ErrorKind.NotFound) {
                                ~~~~~~~~~

Property 'params' does not exist on type 'Context<Record<string, any>>'

The documentation shows "params" as a variable attached to the "context", but the type definition is missing the params for some reason, so the question is, are there two different types of context in the module? why does using context without explicit type works, but when giving an explicit type it fails?

using deno 1.0

import { Context, Router } from "https://deno.land/x/oak/mod.ts";

const router = new Router();

// Breaks when compiling...
router.get('/', (ctx: Context) => {
  console.log(ctx.params);
})

// Doesn't break when compiling
router.get('/', ctx => {
  console.log(ctx.params);
})

Seems like the issue is simply that the field doesn't exist on the Context class, so to fix it we just need to add "params: Record<string, any>" to the class in this file...
https://github.com/oakserver/oak/blob/master/context.ts

Q&A: Router prefix problem

Hello!
So I've been trying to use oak to tinker arround and found either a bug or I'm using it in an incorrect way.
I have the following router :

const TodoRouter = new Router({
    prefix: '/api/todo'
}); 

TodoRouter.get('/' , async (ctx) => {
    console.log('api')
    ctx.response.body = "Hello world"
} )

What I expected : doing a Get request to localhost:port/api/todo would return "Hello World"
What happened : Doing a Get Request to localhost:port/api/todo return nothing
image

So my Question is, when using a Router without a Prefix, the '/' route, work for either localhost:port and localhost:port/ so I would assume that with a prefix works the same, with that in mind is this the correct behaviour or a bug?
Thanks in advance

Proxy middleware

I'd like to use Oak to proxy API requests onto another server, while overriding some with other responses and/or serving up local static assets for others. A proxy middleware would be nice.

TypeScript compiler error in Router example in README.md

Steps to reproduce

  1. Copy exmaple from https://deno.land/x/oak/#router
  2. Run code
error TS2345: Argument of type 'string | undefined' is not assignable to parameter of type 'string'.
  Type 'undefined' is not assignable to type 'string'.

► file:///Users/calebmchenry/code/sandbox/deno-server/src/server.ts:20:41

20       context.response.body = books.get(context.params.id);

Edit:
deno --version

deno 0.40.0
v8 8.2.308
typescript 3.8.3

Feature Request: Cookie support

I hope some easy cookie handler could be implemented just like koa did.

app.use(async (ctx) => {
  ctx.cookies.get('key'); // => "value" | undefined

  ctx.cookies.set('key', 'value', { httpOnly: true });
  // => set `Set-Cookie` header automatically
})

Deno on Win 10 TS Error

Hi Everyone,

I am try Oak middleware as as example below:

import { Application } from "https://deno.land/x/oak/mod.ts";
const app = new Application();
app.use((ctx) => {
  ctx.response.body = "Hello world!";
});
await app.listen("127.0.0.1:8000");

I already install typescript as global npm install -g typescript, and run Deno on terminal with deno --allow-net .\oak.ts, my operating system Win 10 is latest version update, and i also try running as Administrator or another user on terminal and with same Error output, anyone can point some best a away..?, thanks in advance

Error appear as below:

Compile file:///D:/deno-sample/oak.ts
error TS2322: Type '(_: Uint8Array) => Promise<number | null>' is not assignable to type '(p: Uint8Array) => Promise<number | unique symbol>'.
  Type 'Promise<number | null>' is not assignable to type 'Promise<number | unique symbol>'.

► https://deno.land/[email protected]/http/_io.ts:10:5

10     read(_: Uint8Array): Promise<number | null> {
       ~~~~
error TS2322: Type '(buf: Uint8Array) => Promise<number | null>' is not assignable to type '(p: Uint8Array) => Promise<number | unique symbol>'.  Type 'Promise<number | null>' is not assignable to type 'Promise<number | unique symbol>'.

► https://deno.land/[email protected]/http/_io.ts:35:12

35   return { read };
              ~~~~
error TS2322: Type '(buf: Uint8Array) => Promise<number | null>' is not assignable to type '(p: Uint8Array) => Promise<number | unique symbol>'.  Type 'Promise<number | null>' is not assignable to type 'Promise<number | unique symbol>'.
► https://deno.land/[email protected]/http/_io.ts:113:12
113   return { read };
               ~~~~
error TS2339: Property 'iter' does not exist on type 'typeof Deno'.

► https://deno.land/[email protected]/http/_io.ts:167:34

167   for await (const chunk of Deno.iter(r)) {
                                     ~~~~

error TS2345: Argument of type 'Reader' is not assignable to parameter of type 'Writer'.
  Property 'write' is missing in type 'Reader' but required in type 'Writer'.
► https://deno.land/[email protected]/http/_io.ts:265:31
265     const n = await Deno.copy(r.body, writer);
                                  ~~~~~~
  'write' is declared here.

    ► $asset$/lib.deno.ns.d.ts:463:5

    463     write(p: Uint8Array): Promise<number>;
            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
error TS2339: Property 'listenTls' does not exist on type 'typeof Deno'.

► https://deno.land/[email protected]/http/server.ts:16:17
16 const { listen, listenTls } = Deno;
                   ~~~~~~~~~
error TS2694: Namespace 'Deno' has no exported member 'ListenTlsOptions'.
► https://deno.land/[email protected]/http/server.ts:289:38
289 export type HTTPSOptions = Omit<Deno.ListenTlsOptions, "transport">;
                                         ~~~~~~~~~~~~~~~~
error TS2694: Namespace 'Deno' has no exported member 'ListenTlsOptions'.
► https://deno.land/[email protected]/http/server.ts:309:26

309   const tlsOptions: Deno.ListenTlsOptions = {
                             ~~~~~~~~~~~~~~~~
error TS2694: Namespace 'Deno' has no exported member 'WriterSync'.
► https://deno.land/[email protected]/io/bufio.ts:8:24
8 type WriterSync = Deno.WriterSync;
                         ~~~~~~~~~~
error TS2469: The '>=' operator cannot be applied to type 'symbol'.

► https://deno.land/[email protected]/io/bufio.ts:90:14

90       assert(rr >= 0, "negative read");
                ~~
error TS2365: Operator '+=' cannot be applied to types 'number' and 'number | unique symbol'.
► https://deno.land/[email protected]/io/bufio.ts:91:7

91       this.w += rr;
         ~~~~~~~~~~~~
error TS2469: The '>' operator cannot be applied to type 'symbol'.
► https://deno.land/[email protected]/io/bufio.ts:92:11

92      if (rr > 0) {
             ~~
error TS2416: Property 'read' in type 'BufReader' is not assignable to the same property in base type 'Reader'.
  Type '(p: Uint8Array) => Promise<number | null>' is not assignable to type '(p: Uint8Array) => Promise<number | unique symbol>'.
    Type 'Promise<number | null>' is not assignable to type 'Promise<number | unique symbol>'.
      Type 'number | null' is not assignable to type 'number | unique symbol'.
        Type 'null' is not assignable to type 'number | unique symbol'.
► https://deno.land/[email protected]/io/bufio.ts:123:9

123   async read(p: Uint8Array): Promise<number | null> {
            ~~~~
error TS2469: The '>=' operator cannot be applied to type 'symbol'.
► https://deno.land/[email protected]/io/bufio.ts:133:16
133         assert(nread >= 0, "negative read");
                   ~~~~~
error TS2322: Type 'number | unique symbol' is not assignable to type 'number | null'.
  Type 'unique symbol' is not assignable to type 'number | null'.
► https://deno.land/[email protected]/io/bufio.ts:138:9
138         return rr;
            ~~~~~~~~~~
error TS2322: Type 'number | unique symbol' is not assignable to type 'number | null'.
  Type 'unique symbol' is not assignable to type 'number | null'.
► https://deno.land/[email protected]/io/bufio.ts:145:7
145       rr = await this.rd.read(this.buf);
          ~~
error TS2367: This condition will always return 'false' since the types 'OperatingSystem' and '"windows"' have no overlap.
► https://deno.land/[email protected]/path/_constants.ts:51:19

51 const isWindows = build.os == "windows";
                     ~~~~~~~~~~~~~~~~~~~~~
error TS2367: This condition will always return 'false' since the types 'OperatingSystem' and '"windows"' have no overlap.
► https://deno.land/[email protected]/path/_globrex.ts:5:15
5 const isWin = Deno.build.os === "windows";
                ~~~~~~~~~~~~~~~~~~~~~~~~~~~
error TS2367: This condition will always return 'false' since the types 'OperatingSystem' and '"windows"' have no overlap.
► https://deno.land/[email protected]/path/mod.ts:7:19
7 const isWindows = Deno.build.os == "windows";
                    ~~~~~~~~~~~~~~~~~~~~~~~~~~
error TS2345: Argument of type 'string | URL' is not assignable to parameter of type 'string'.
  Type 'URL' is not assignable to type 'string'.
► https://deno.land/[email protected]/path/posix.ts:433:18

433   return new URL(url).pathname;
                     ~~~
error TS2367: This condition will always return 'false' since the types 'OperatingSystem' and '"windows"' have no overlap.
► https://deno.land/[email protected]/path/separator.ts:2:19

2 const isWindows = Deno.build.os == "windows";
                    ~~~~~~~~~~~~~~~~~~~~~~~~~~
error TS2339: Property 'get' does not exist on type '{ (): { [index: string]: string; }; (key: string): string | undefined; }'.
► https://deno.land/[email protected]/path/win32.ts:42:18

42       path = env.get(`=${resolvedDevice}`) || cwd();
                    ~~~
error TS2345: Argument of type 'string | URL' is not assignable to parameter of type 'string'.
  Type 'URL' is not assignable to type 'string'.
► https://deno.land/[email protected]/path/win32.ts:911:18

911   return new URL(url).pathname
                     ~~~
error TS2304: Cannot find name 'AbortSignal'.
► https://deno.land/x/oak/application.ts:18:12

18   signal?: AbortSignal;
              ~~~~~~~~~~~
error TS2304: Cannot find name 'AbortSignal'.
► https://deno.land/x/oak/application.ts:27:12
27   signal?: AbortSignal;
              ~~~~~~~~~~~
error TS2304: Cannot find name 'ErrorEventInit'.

► https://deno.land/x/oak/application.ts:45:11

45   extends ErrorEventInit {
             ~~~~~~~~~~~~~~
error TS2304: Cannot find name 'ErrorEvent'.

► https://deno.land/x/oak/application.ts:78:61
78 export class ApplicationErrorEvent<S extends State> extends ErrorEvent {
                                                               ~~~~~~~~~~
error TS2345: Argument of type '{ context: Context<S>; message: any; error: any; }' is not assignable to parameter of type 'ApplicationErrorEventInit<S>'.
  Object literal may only specify known properties, and 'message' does not exist in type 'ApplicationErrorEventInit<S>'.

► https://deno.land/x/oak/application.ts:153:53

153       new ApplicationErrorEvent("error", { context, message, error }),
                                                        ~~~~~~~

error TS2416: Property 'addEventListener' in type 'Application<S>' is not assignable to the same property in base type 'EventTarget'.
  Type '(type: "error", listener: ApplicationErrorEventListener<S> | ApplicationErrorEventListenerObject<S> | null, options?: any) => void' is not assignable to type '(type: string, callback: EventListener | EventListenerObject | null, options?: boolean | AddEventListenerOptions | undefined) => void'.
    Types of parameters 'listener' and 'callback' are incompatible.
      Type 'EventListener | EventListenerObject | null' is not assignable to type 'ApplicationErrorEventListener<S> | ApplicationErrorEventListenerObject<S> | null'.
        Type 'EventListener' is not assignable to type 'ApplicationErrorEventListener<S> | ApplicationErrorEventListenerObject<S> | null'.      
          Type 'EventListener' is not assignable to type 'ApplicationErrorEventListener<S>'.
            Types of parameters 'evt' and 'evt' are incompatible.
              Type 'ApplicationErrorEvent<S>' is missing the following properties from type 'Event': type, target, currentTarget, composedPath, 
and 17 more.
► https://deno.land/x/oak/application.ts:202:3

202   addEventListener(
      ~~~~~~~~~~~~~~~~
error TS2304: Cannot find name 'AddEventListenerOptions'.

► https://deno.land/x/oak/application.ts:205:25

205     options?: boolean | AddEventListenerOptions,
                            ~~~~~~~~~~~~~~~~~~~~~~~
error TS2416: Property 'addEventListener' in type 'Application<S>' is not assignable to the same property in base type 'EventTarget'.
  Type '(type: "error", listener: ApplicationErrorEventListener<S> | ApplicationErrorEventListenerObject<S> | null, options?: any) => void' is not assignable to type '(type: string, callback: EventListener | EventListenerObject | null, options?: boolean | AddEventListenerOptions | undefined) => void'.

► https://deno.land/x/oak/application.ts:207:3
207   addEventListener(
      ~~~~~~~~~~~~~~~~
error TS2304: Cannot find name 'EventListenerOrEventListenerObject'.
► https://deno.land/x/oak/application.ts:209:15

209     listener: EventListenerOrEventListenerObject | null,
                  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
error TS2304: Cannot find name 'AddEventListenerOptions'.

► https://deno.land/x/oak/application.ts:210:25

210     options?: boolean | AddEventListenerOptions,
                            ~~~~~~~~~~~~~~~~~~~~~~~

error TS2345: Argument of type '{ message: string; error: any; }' is not assignable to parameter of type 'ApplicationErrorEventInit<Record<string | number | symbol, any>>'.
  Object literal may only specify known properties, and 'message' does not exist in type 'ApplicationErrorEventInit<Record<string | number | symbol, any>>'.

► https://deno.land/x/oak/application.ts:254:46

254         new ApplicationErrorEvent("error", { message, error }),
                                                 ~~~~~~~
error TS2322: Type '() => boolean' is not assignable to type 'boolean'.
► https://deno.land/x/oak/send.ts:62:12
62     return (await Deno.stat(path)).isFile;
              ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  Did you mean to call this expression?
    ► https://deno.land/x/oak/send.ts:62:12
    62     return (await Deno.stat(path)).isFile;
                  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
error TS2774: This condition will always return true since the function is always defined. Did you mean to call it instead?

► https://deno.land/x/oak/send.ts:135:9

135     if (stats.isDirectory) {
            ~~~~~~~~~~~~~~~~~

error TS2339: Property 'mtime' does not exist on type 'FileInfo'.

► https://deno.land/x/oak/send.ts:151:55

151   if (!response.headers.has("Last-Modified") && stats.mtime) {
                                                          ~~~~~
error TS2339: Property 'mtime' does not exist on type 'FileInfo'.

► https://deno.land/x/oak/send.ts:152:49

152     response.headers.set("Last-Modified", stats.mtime.toUTCString());
                                                    ~~~~~
Found 38 errors.

Serving static files

Trying to serve static files using this code snippet in the README.md yield the following error.

app.use(async (context) => { await send(context, context.request.path, { root: "./static", index: "index.html", }); });

Error.
error TS2339: Property 'path' does not exist on type 'Request'. await send(context, context.request.path, {

Router prefix

Based on the RouterOptions interface, it seems like I should be able to specify a prefix for a router by declaring a new router like so:

const router = new Router({
    prefix: 'example',
});

However, it seems the Router constructor doesn't actually do anything with the prefix router option.

I cloned the repo and fixed this for personal use. If you're interested I could create a PR. Do you already have this addition on your radar?

Wrong concurrency model

This is a piece of code from Oak:

https://github.com/oakserver/oak/blob/master/application.ts#L25-L35

This incorrectly uses await for each request. As a result, requests cannot be executed in parallel, but are executed serially. If two requests are sent in succession, each of which takes 10s, eventually all requests will be completed in 20s instead of all being completed at the same time in 10s

import { Application } from "https://deno.land/x/oak/mod.ts";
const app = new Application();
app.use(async ctx => {
  await new Promise(resolve => setTimeout(resolve, 10000));
  ctx.response.body = "Hello World!";
});
await app.listen({ port: 8000 });

Use tag releases for ease of use

Hey @kitsonk !

Currently I'm using git commit hash to fix to a version (ex: Deno v0.42, 1.0.0 rc2, etc) so I can use it in my project, but it would be way better to have a tag to fix to.

Can't run Hello World example

I have copy pasted from the docs to to remove my sketchy typing from the equation :)

import { Application } from "https://deno.land/x/oak/mod.ts";

const app = new Application();

app.use((ctx) => {
  ctx.response.body = "Hello world!";
});

await app.listen("127.0.0.1:8000");

then running deno run --allow-net server.ts

I get 25 errors after compilation that I can paste below for reference:

error TS2322: Type '(_: Uint8Array) => Promise<number | null>' is not assignable to type '(p: Uint8Array) => Promise<number | unique symbol>'.
Type 'Promise<number | null>' is not assignable to type 'Promise<number | unique symbol>'.

https://deno.land/[email protected]/http/_io.ts:10:5

10 read(_: Uint8Array): Promise<number | null> {
~~~~

error TS2322: Type '(buf: Uint8Array) => Promise<number | null>' is not assignable to type '(p: Uint8Array) => Promise<number | unique symbol>'.
Type 'Promise<number | null>' is not assignable to type 'Promise<number | unique symbol>'.

https://deno.land/[email protected]/http/_io.ts:35:12

35 return { read };
~~~~

error TS2322: Type '(buf: Uint8Array) => Promise<number | null>' is not assignable to type '(p: Uint8Array) => Promise<number | unique symbol>'.
Type 'Promise<number | null>' is not assignable to type 'Promise<number | unique symbol>'.

https://deno.land/[email protected]/http/_io.ts:113:12

113 return { read };
~~~~

error TS2339: Property 'iter' does not exist on type 'typeof Deno'.

https://deno.land/[email protected]/http/_io.ts:167:34

167 for await (const chunk of Deno.iter(r)) {
~~~~

error TS2345: Argument of type 'Reader' is not assignable to parameter of type 'Writer'.
Property 'write' is missing in type 'Reader' but required in type 'Writer'.

https://deno.land/[email protected]/http/_io.ts:265:31

265 const n = await Deno.copy(r.body, writer);
~~~~~~

'write' is declared here.

► $asset$/lib.deno.ns.d.ts:504:5

504     write(p: Uint8Array): Promise<number>;
        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

error TS2339: Property 'listenTls' does not exist on type 'typeof Deno'.

https://deno.land/[email protected]/http/server.ts:16:17

16 const { listen, listenTls } = Deno;
~~~~~~~~~

error TS2694: Namespace 'Deno' has no exported member 'ListenTlsOptions'.

https://deno.land/[email protected]/http/server.ts:289:38

289 export type HTTPSOptions = Omit<Deno.ListenTlsOptions, "transport">;
~~~~~~~~~~~~~~~~

error TS2694: Namespace 'Deno' has no exported member 'ListenTlsOptions'.

https://deno.land/[email protected]/http/server.ts:309:26

309 const tlsOptions: Deno.ListenTlsOptions = {
~~~~~~~~~~~~~~~~

error TS2694: Namespace 'Deno' has no exported member 'WriterSync'.

https://deno.land/[email protected]/io/bufio.ts:8:24

8 type WriterSync = Deno.WriterSync;
~~~~~~~~~~

error TS2469: The '>=' operator cannot be applied to type 'symbol'.

https://deno.land/[email protected]/io/bufio.ts:90:14

90 assert(rr >= 0, "negative read");
~~

error TS2365: Operator '+=' cannot be applied to types 'number' and 'number | unique symbol'.

https://deno.land/[email protected]/io/bufio.ts:91:7

91 this.w += rr;
~~~~~~~~~~~~

error TS2469: The '>' operator cannot be applied to type 'symbol'.

https://deno.land/[email protected]/io/bufio.ts:92:11

92 if (rr > 0) {
~~

error TS2416: Property 'read' in type 'BufReader' is not assignable to the same property in base type 'Reader'.
Type '(p: Uint8Array) => Promise<number | null>' is not assignable to type '(p: Uint8Array) => Promise<number | unique symbol>'.
Type 'Promise<number | null>' is not assignable to type 'Promise<number | unique symbol>'.
Type 'number | null' is not assignable to type 'number | unique symbol'.
Type 'null' is not assignable to type 'number | unique symbol'.

https://deno.land/[email protected]/io/bufio.ts:123:9

123 async read(p: Uint8Array): Promise<number | null> {
~~~~

error TS2469: The '>=' operator cannot be applied to type 'symbol'.

https://deno.land/[email protected]/io/bufio.ts:133:16

133 assert(nread >= 0, "negative read");
~~~~~

error TS2322: Type 'number | unique symbol' is not assignable to type 'number | null'.
Type 'unique symbol' is not assignable to type 'number | null'.

https://deno.land/[email protected]/io/bufio.ts:138:9

138 return rr;
~~~~~~~~~~

error TS2322: Type 'number | unique symbol' is not assignable to type 'number | null'.
Type 'unique symbol' is not assignable to type 'number | null'.

https://deno.land/[email protected]/io/bufio.ts:145:7

145 rr = await this.rd.read(this.buf);
~~

error TS2367: This condition will always return 'false' since the types 'OperatingSystem' and '"windows"' have no overlap.

https://deno.land/[email protected]/path/_constants.ts:51:19

51 const isWindows = build.os == "windows";
~~~~~~~~~~~~~~~~~~~~~

error TS2367: This condition will always return 'false' since the types 'OperatingSystem' and '"windows"' have no overlap.

https://deno.land/[email protected]/path/_globrex.ts:5:15

5 const isWin = Deno.build.os === "windows";
~~~~~~~~~~~~~~~~~~~~~~~~~~~

error TS2367: This condition will always return 'false' since the types 'OperatingSystem' and '"windows"' have no overlap.

https://deno.land/[email protected]/path/mod.ts:7:19

7 const isWindows = Deno.build.os == "windows";
~~~~~~~~~~~~~~~~~~~~~~~~~~

error TS2345: Argument of type 'string | URL' is not assignable to parameter of type 'string'.
Type 'URL' is not assignable to type 'string'.

https://deno.land/[email protected]/path/posix.ts:433:18

433 return new URL(url).pathname;
~~~

error TS2367: This condition will always return 'false' since the types 'OperatingSystem' and '"windows"' have no overlap.

https://deno.land/[email protected]/path/separator.ts:2:19

2 const isWindows = Deno.build.os == "windows";
~~~~~~~~~~~~~~~~~~~~~~~~~~

error TS2339: Property 'get' does not exist on type '{ (): { [index: string]: string; }; (key: string): string | undefined; }'.

https://deno.land/[email protected]/path/win32.ts:42:18

42 path = env.get(=${resolvedDevice}) || cwd();
~~~

error TS2345: Argument of type 'string | URL' is not assignable to parameter of type 'string'.
Type 'URL' is not assignable to type 'string'.

https://deno.land/[email protected]/path/win32.ts:911:18

911 return new URL(url).pathname
~~~

error TS2339: Property 'mtime' does not exist on type 'FileInfo'.

https://deno.land/x/oak/send.ts:151:55

151 if (!response.headers.has("Last-Modified") && stats.mtime) {
~~~~~

error TS2339: Property 'mtime' does not exist on type 'FileInfo'.

https://deno.land/x/oak/send.ts:152:49

152 response.headers.set("Last-Modified", stats.mtime.toUTCString());
~~~~~

Cookies not working

Cookies are not working.

Example:

.get(
    '/setCookie',
    (context) => {
      context.cookies.set('auth', JSON.stringify({count:1}))
      context.response.body = 'cookie set'
    }
  )
  .get(
    '/getCookie',
    (context) => {
      context.response.body = 'hello' + context.cookies.get('auth')
    }
  )

This doesn't return the cookie value on the second request.

Route handlers with query params not matching

Hi,

Apologies if am doing this incorrectly. Noticing route handlers with query params are not matching.

Can reproduce this through examples/routingServer.ts:

  1. Remove the code block 40-42:
  // .get("/book", async (context) => {
  //   context.response.body = Array.from(books.values());
  // })
  1. Query the server (am using postman): GET http://127.0.0.1:8000/book?id=1234
  2. Observe that the server returns 404 Not Found

🐞 Can't run examples

Both of the examples fail with both Deno 0.10 and 0.11 with the following errors;

$ deno index.ts --allow-read --allow-net
Compile file:///Users/bevan/projects/denoworld/index.ts

error TS2694: Namespace 'Deno' has no exported member 'ReadResult'.

► https://deno.land/[email protected]/io/bufio.ts:7:24

7 type ReadResult = Deno.ReadResult;
                         ~~~~~~~~~~

error TS2339: Property 'nread' does not exist on type 'number'.

► https://deno.land/[email protected]/io/bufio.ts:134:19

134         assert(rr.nread >= 0, "negative read");
                      ~~~~~


Found 2 errors.

On macos 10.13.6 High Sierra;

$ deno version
deno: 0.12.0
v8: 7.7.200
typescript: 3.5.1

Is it possible to handle BrokenPipe errors?

There doesn't seem to be a way to handle BrokenPipe errors thrown in std/http/server.ts for individual requests from oak without modifying the Application class. The error occurs if the HTTP client closes the connection prematurely.

Here is an example.

import { Application, Router } from "https://deno.land/x/oak/mod.ts";

console.log("Starting...");

const router = new Router();
const veryLong = "foo bar baz qux ".repeat(1000000);

router.get("/demo", async ctx => {
  ctx.response.type = "text/html; charset=utf-8";
  ctx.response.body = `<h1>Hello!</h1>\n${veryLong}`;
});

const app = new Application();
app
  .use(router.routes())
  .use(router.allowedMethods())
  .listen("localhost:8080");

Save the code above as demo.ts and run the following shell commands to reproduce the error.

> deno --version
deno 0.31.0
v8 8.1.108
typescript 3.7.2
> deno --allow-net demo.ts &
> Compile file:///home/dbohdan/demo.ts
Starting...
> curl http://localhost:8080/demo & sleep 0.1; killall curl
> error: Uncaught BrokenPipe: Broken pipe (os error 32)   
► $deno$/dispatch_minimal.ts:59:11
    at DenoError ($deno$/errors.ts:20:5)
    at unwrapResponse ($deno$/dispatch_minimal.ts:59:11)
    at sendAsyncMinimal ($deno$/dispatch_minimal.ts:102:10)
fish: Job 2, “curl http://localhost:8080/demo…” terminated by signal SIGTERM (Polite quit request)
Job 1, 'deno --allow-net demo.ts &' has ended

Sub-routers

The idea is to be able to use a router as path of an another router. Maybe this is supported, if so, sorry for creating a duplicate issue.

Q&A: How to get list of implemented routes for router?

How can I get a list of all the routes that my router has available?
Guiding myself from the self question in Koa something like this can be done:

router.get('/', async (context: RouterContext) => {
  context.response.body = "This is a response";
});

router.stack.map(i => i.path);

[Proposal] Better type support

Since this project followed koa's design, it has the same problem on types. Here I have a proposal to bring better type support for middleware.

The problem is that you cannot extend Application's type parameter when applying middlewares(which attach extra properties to ctx.state) on it. For example:

const app = new Application()

app.use(ctx => {
    ctx.state.username = 'foo'
}).use(ctx => {
    // not type safe for username
    ctx.state.username
})

My idea is we can change the prototype of use like this:

class Application<S>{
  use<NS extends {} = S>(middleware: Middleware<NS, Context<NS>>): Application<NS extends S ? NS : (S & NS)>
}

So we can happily write middleware with type checking:

// when need to extend context type, declare only what you want to attach
app.use((ctx: Context<{ name: string }>) => {
  ctx.state.name = 'foo'
  // I'm not including next here for simplification 
})
// extend again
.use((ctx: Context<{ id: string }>) => {
  ctx.state.id = 'id'
})
// context type is by default according to last middleware
.use((ctx) => {
  // ctx has a default type
  ctx.state.id = 'id'
  ctx.state.name = 'name'
})

It's not hard to do so if we don't support applying multiple middleware by one use call(which should be a rare use case).

And it's a little complicated for Router, we also want Router to have same context type as Application, so the Router instance must be created by a Application instance, so its type parameter can be inherited.

Application<S> {
  createRouter(): Router<S>
}

If you're good with this, I'll be working on it and send a pull request. Or please reply if have any comments.

ctx.request.body() is not working

Thank you for this beautiful work!

Anyway I call ctx.request.body() forces the endpoint to Error out and not console.log() or add a response body

import { Application, Router } from "https://deno.land/x/oak/mod.ts"

let port = 8084,
    router = new Router(),
    app = new Application()

router
  .post('/wtf', async ctx => {
    let body = await ctx.request.body()
    console.log(body)
    ctx.response.body = { hello: 'world' }
  })

app.use(router.routes())
app.use(router.allowedMethods())

app.listen(`0.0.0.0:${ port }`)

Q&A: Can you close the server manually?

Thanks for maintaining this project!

I want to close down the Oak server each test.

application.use(...routes);
application.listen({ port });
test_code(port); 
application.close(); // <-- Something like this 

Is this possible?

Olav

`Transfer-Encoding:chunked` in request headers causes `UnexpectedEOFError`

I tried to send HTTP GET request with Transfer-encoding: chunked in the request header such as following command then.

$ curl "http://localhost:8000" -H "Transfer-encoding: chunked"

Then http server process faced UnexpectedEOFError.

error: Uncaught UnexpectedEOFError: Unexpected EOF
► io.ts:66:34

66     if (line === Deno.EOF) throw new UnexpectedEOFError();
                                    ^

    at UnexpectedEOFError (bufio.ts:28:5)
    at read (io.ts:66:34)

server.ts is following.

import { Application } from "https://deno.land/x/oak/mod.ts";

const app = new Application();

app.use(ctx => {
  ctx.response.body = "Hello World!";
});

await app.listen({ port: 8000 });

errorStatus and Status are not comparable

The example for ErrorHandling in the readme does not work, stating that Type 'Status.NotFound' is not comparable to type 'ErrorStatus'.

This is the said part of the readme

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;
    }
  }
});

The fix that I could come up with is not pretty and is to cast err.status as Status but that is only possible by casting into unknown first and then into Status

switch (err.status as unknown as Status) {

Am I missing something?

error: Uncaught PermissionDenied: network access to "127.0.0.1:8000", run again with the --allow-net flag

Hey, guys, how are you?
I'm starting my studies with the deno and I came across the following message: what could be wrong?

peidrao in deno on  master [?] took 11s 
❯ sudo deno helloDenos.ts
Compile file:///home/peidrao/deno/helloDenos.ts
error: Uncaught PermissionDenied: network access to "127.0.0.1:3000", run again with the --allow-net flag
► $deno$/ops/dispatch_json.ts:43:11
    at PermissionDenied ($deno$/errors.ts:81:5)
    at unwrapResponse ($deno$/ops/dispatch_json.ts:43:11)
    at sendSync ($deno$/ops/dispatch_json.ts:72:10)
    at listen ($deno$/ops/net.ts:50:10)
    at listen ($deno$/net.ts:168:18)
    at serve (server.ts:251:20)
    at listen (application.ts:29:25)
    at helloDenos.ts:8:5

Error: Type 'Boolean' has no call signatures.

What was done

A file called app.ts was created with the following code:

import { Application } from "https://deno.land/x/oak/mod.ts";

const app = new Application();

app.use(ctx => {
  ctx.response.body = "Hello World!";
});

console.log("Listening.....");
await app.listen({ port: 8000 });

Subsequently, the following command was executed:

deno --allow-net app.ts

Expected output

Expected deno to compile the code successfully and see the following line in stdout:

Listening.....

Actual Output

Compilation failed with the following error:

Compile file:///home/angad/Desktop/covidcomm/covidcomm/app.ts
error TS2349: This expression is not callable.
  Type 'Boolean' has no call signatures.

► https://deno.land/x/oak/send.ts:62:36

62     return (await Deno.stat(path)).isFile();
                                      ~~~~~~

error TS2349: This expression is not callable.
  Type 'Boolean' has no call signatures.

► https://deno.land/x/oak/send.ts:139:15

139     if (stats.isDirectory()) {
                  ~~~~~~~~~~~


Found 2 errors.

Error screenshot:
image

Environment

Package Version
deno 0.41.0
v8 8.2.308
typescript 3.8.3

image

GraphQL

It will be nice to have GraphQL middleware. I'd like to help with it.

Q&A: Middleware for error handling

If I wanted to throw a 500 error through my oak based API, how would I catch an error an being able to return it without Deno throwing as well?

Something like this in oak, would be nice:

app.use(async (ctx, next) => {
  try {
    await next();
  } catch (err) {
    //Resolve error here
  }
});

Router post does not work.

Hi,

I am new to deno and oak.

The below code doesnt work for post method but WORKS for get.

import { Application, Router } from "https://deno.land/x/oak/mod.ts";

const router = new Router();
router
  .post("/", context => {
    context.response.body = "Hello world Johnson!";
  });

const app = new Application();
app.use(router.routes());
app.use(router.allowedMethods());

await app.listen("127.0.0.1:8000");

Any thoughts on this? Thanks in advance.

Request body middleware

I wanted to make simple REST API with oak but noticed that there is no body field on Request, or worse any way to access underlying ServerRequest's body. I guess body parsing middleware is missing.

Bodyless response

I'm developing simple API with oak, I want to return simple 404 response.

function notFound(context: Context) {
    context.response.status = Status.NotFound;
}

code above causes server to hang because there is no body in response.

If you do:

function notFound(context: Context) {
    context.response.status = Status.NotFound;
    context.response.body = "";
}

everything's working correctly.

Is this a bug or did I forget about something?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.