Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

curveball

Package Overview
Dependencies
Maintainers
1
Versions
6
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

curveball - npm Package Compare versions

Comparing version 0.2.0 to 0.3.0

dist/node-http-utils.d.ts

15

changelog.md

@@ -0,1 +1,15 @@

0.3.0 (2018-06-26)
==================
* #5: Support for informational status codes such as `100 Continue` and
`103 Early Hints` for both HTTP/1 and HTTP/2.
* #28: HTTP2 support.
* #34: `Application` is now the default export.
* #47: `Application.callback` now returns a callback instead of implementing
it. This makes it a bit easier to deal with `this` scope and is also
consistent with Koa.
* #48: Added a setter for `Response.status()`.
* Now exporting the `Middleware` type.
0.2.0 (2018-06-25)

@@ -12,2 +26,3 @@ ==================

0.1.2 (2018-06-24)

@@ -14,0 +29,0 @@ ==================

33

dist/application.d.ts
/// <reference types="node" />
import EventEmitter from 'events';
import http from 'http';
import Context from './context';
import EventEmitter from 'events';
declare type Middleware = (ctx: Context, next: () => Promise<void>) => Promise<void> | void;
import { HttpCallback } from './node-http-utils';
/**
* The Middleware function is implemented by all middlewares.
*
* The Middleware function takes a Context and a next() function as its
* arguments, and _may_ be an async function.
*/
export declare type Middleware = (ctx: Context, next: () => Promise<void>) => Promise<void> | void;
export default class Application extends EventEmitter {

@@ -14,9 +21,2 @@ middlewares: Middleware[];

/**
* Calls a chain of middlewares.
*
* Pass a list of middlewares. It will call the first and bind the next
* middleware to next().
*/
private callMiddleware;
/**
* Starts a HTTP server on the specified port.

@@ -29,5 +29,14 @@ */

*/
callback(req: http.IncomingMessage, res: http.ServerResponse): Promise<void>;
createContext(req: http.IncomingMessage, res: http.ServerResponse): Context;
callback(): HttpCallback;
/**
* Creates a Context object based on a node.js request and response object.
*/
private createContext;
/**
* Calls a chain of middlewares.
*
* Pass a list of middlewares. It will call the first and bind the next
* middleware to next().
*/
private callMiddleware;
}
export {};

@@ -6,7 +6,7 @@ "use strict";

Object.defineProperty(exports, "__esModule", { value: true });
const events_1 = __importDefault(require("events"));
const http_1 = __importDefault(require("http"));
const context_1 = __importDefault(require("./context"));
const node_request_1 = __importDefault(require("./node-request"));
const node_response_1 = __importDefault(require("./node-response"));
const context_1 = __importDefault(require("./context"));
const events_1 = __importDefault(require("events"));
const pkg = require('../package.json');

@@ -29,20 +29,6 @@ class Application extends events_1.default {

/**
* Calls a chain of middlewares.
*
* Pass a list of middlewares. It will call the first and bind the next
* middleware to next().
*/
async callMiddleware(ctx, fns) {
if (fns.length === 0) {
return;
}
return fns[0](ctx, async () => {
await this.callMiddleware(ctx, fns.slice(1));
});
}
/**
* Starts a HTTP server on the specified port.
*/
listen(port) {
const server = http_1.default.createServer(this.callback.bind(this));
const server = http_1.default.createServer(this.callback());
return server.listen(port);

@@ -54,24 +40,34 @@ }

*/
async callback(req, res) {
try {
const ctx = this.createContext(req, res);
await this.handle(ctx);
if (typeof ctx.response.body === 'string') {
res.write(ctx.response.body);
callback() {
return async (req, res) => {
try {
const ctx = this.createContext(req, res);
await this.handle(ctx);
if (typeof ctx.response.body === 'string') {
// @ts-ignore
res.write(ctx.response.body);
}
else {
throw new Error('Only strings are supported currently');
}
// @ts-ignore
res.end();
}
else {
throw new Error('Only strings are supported currently');
catch (err) {
// tslint:disable:no-console
console.error(err);
res.statusCode = 500;
// @ts-ignore
res.write('Uncaught exception');
// @ts-ignore
res.end();
if (this.listenerCount('error')) {
this.emit('error', err);
}
}
res.end();
}
catch (err) {
console.error(err);
res.statusCode = 500;
res.write('Uncaught exception');
res.end();
if (this.listenerCount('error')) {
this.emit('error', err);
}
}
};
}
/**
* Creates a Context object based on a node.js request and response object.
*/
createContext(req, res) {

@@ -81,4 +77,18 @@ const context = new context_1.default(new node_request_1.default(req), new node_response_1.default(res));

}
/**
* Calls a chain of middlewares.
*
* Pass a list of middlewares. It will call the first and bind the next
* middleware to next().
*/
async callMiddleware(ctx, fns) {
if (fns.length === 0) {
return;
}
return fns[0](ctx, async () => {
await this.callMiddleware(ctx, fns.slice(1));
});
}
}
exports.default = Application;
//# sourceMappingURL=application.js.map

@@ -23,4 +23,19 @@ /**

delete(name: string): void;
/**
* Returns all HTTP headers.
*
* Headernames are lowercased. Values may be either strings or arrays of
* strings or numbers.
*/
getAll(): HeadersObject;
/**
* Appends a new header, without removing an old one with the same name.
*/
append(name: string, value: string | string[] | number): void;
}
declare type HeadersObj = {
/**
* This type is a simple key-value object that can be used to instantiate a
* Headers class.
*/
export declare type HeadersObject = {
[headerName: string]: string | string[] | number;

@@ -30,3 +45,3 @@ };

private store;
constructor(headersObj?: HeadersObj);
constructor(headersObj?: HeadersObject);
/**

@@ -47,2 +62,13 @@ * Sets a HTTP header name and value.

/**
* Returns all HTTP headers.
*
* Headernames are lowercased. Values may be either strings or arrays of
* strings or numbers.
*/
getAll(): HeadersObject;
/**
* Appends a new header, without removing an old one with the same name.
*/
append(name: string, value: string | string[] | number): void;
/**
* Removes a HTTP header

@@ -49,0 +75,0 @@ */

@@ -37,3 +37,3 @@ "use strict";

else if (Array.isArray(value)) {
return value.join(',');
return value.join(', ');
}

@@ -45,6 +45,31 @@ else {

/**
* Returns all HTTP headers.
*
* Headernames are lowercased. Values may be either strings or arrays of
* strings or numbers.
*/
getAll() {
const result = {};
for (const headerName of Object.keys(this.store)) {
result[headerName] = this.store[headerName][1];
}
return result;
}
/**
* Appends a new header, without removing an old one with the same name.
*/
append(name, value) {
const lowerName = name.toLowerCase();
if (this.store[lowerName] === undefined) {
this.store[lowerName] = [name, value];
return;
}
const oldArray = Array.isArray(this.store[lowerName][1]) ? this.store[lowerName][1] : [this.store[lowerName][1].toString()];
this.store[lowerName][1] = oldArray.concat(value);
}
/**
* Removes a HTTP header
*/
delete(name) {
this.store[name.toLowerCase()] = undefined;
delete this.store[name.toLowerCase()];
}

@@ -51,0 +76,0 @@ }

@@ -1,5 +0,8 @@

export { default as Application } from './application';
export { default as Context } from './context';
export { default as Headers } from './headers';
export { default as Request } from './request';
export { default as Response } from './response';
import Application from './application';
import { Middleware } from './application';
import Context from './context';
import Headers from './headers';
import Request from './request';
import Response from './response';
export default Application;
export { Application, Context, Headers, Middleware, Request, Response, };
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
var application_1 = require("./application");
const application_1 = __importDefault(require("./application"));
exports.Application = application_1.default;
var context_1 = require("./context");
const context_1 = __importDefault(require("./context"));
exports.Context = context_1.default;
var headers_1 = require("./headers");
const headers_1 = __importDefault(require("./headers"));
exports.Headers = headers_1.default;
exports.default = application_1.default;
//# sourceMappingURL=index.js.map
/// <reference types="node" />
import http from 'http';
import { HeadersInterface } from './headers';
import { NodeHttpRequest } from './node-http-utils';
import Request from './request';
import { HeadersInterface } from './headers';
export declare class NodeRequest implements Request {

@@ -11,6 +11,11 @@ /**

/**
* Contains a parsed, stored representation of the body. It's up to
* middlewares to do the actual parsing.
*/
body: any;
/**
* Node.js Request object
*/
private inner;
constructor(inner: http.IncomingMessage);
constructor(inner: NodeHttpRequest);
/**

@@ -53,7 +58,2 @@ * path-part of the request.

/**
* Contains a parsed, stored representation of the body. It's up to
* middlewares to do the actual parsing.
*/
body: any;
/**
* This function returns the request body.

@@ -60,0 +60,0 @@ *

@@ -6,6 +6,6 @@ "use strict";

Object.defineProperty(exports, "__esModule", { value: true });
const headers_1 = require("./headers");
const url_1 = __importDefault(require("url"));
const accepts_1 = __importDefault(require("accepts"));
const raw_body_1 = __importDefault(require("raw-body"));
const url_1 = __importDefault(require("url"));
const headers_1 = require("./headers");
class NodeRequest {

@@ -86,4 +86,5 @@ constructor(inner) {

const type = this.headers.get('content-type');
if (!type)
if (!type) {
return '';
}
return type.split(';')[0];

@@ -90,0 +91,0 @@ }

@@ -1,5 +0,4 @@

/// <reference types="node" />
import http from 'http';
import { HeadersInterface, HeadersObject } from './headers';
import { NodeHttpResponse } from './node-http-utils';
import Response from './response';
import { HeadersInterface } from './headers';
/**

@@ -11,3 +10,3 @@ * This is a wrapper around the Node Response object, and handles creates a

private inner;
constructor(inner: http.ServerResponse);
constructor(inner: NodeHttpResponse);
/**

@@ -31,6 +30,17 @@ * Sets a HTTP header name and value

delete(name: string): void;
/**
* Returns all HTTP headers.
*
* Headernames are not lowercased. Values may be either strings or arrays of
* strings.
*/
getAll(): HeadersObject;
/**
* Appends a new header, without removing an old one with the same name.
*/
append(name: string, value: string | string[] | number): void;
}
export declare class NodeResponse implements Response {
private inner;
constructor(inner: http.ServerResponse);
constructor(inner: NodeHttpResponse);
/**

@@ -43,4 +53,7 @@ * List of HTTP Headers

*/
readonly status: number;
/**
* Updates the HTTP status code for this response.
*/
status: number;
/**
* The response body.

@@ -56,3 +69,10 @@ */

readonly type: string;
/**
* Sends an informational response before the real response.
*
* This can be used to for example send a `100 Continue` or `103 Early Hints`
* response.
*/
sendInformational(status: number, headers?: HeadersInterface | HeadersObject): Promise<void>;
}
export default NodeResponse;
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const http_1 = __importDefault(require("http"));
const util_1 = require("util");
const node_http_utils_1 = require("./node-http-utils");
/**

@@ -35,3 +41,3 @@ * This is a wrapper around the Node Response object, and handles creates a

else if (Array.isArray(value)) {
return value.join(',');
return value.join(', ');
}

@@ -48,2 +54,24 @@ else {

}
/**
* Returns all HTTP headers.
*
* Headernames are not lowercased. Values may be either strings or arrays of
* strings.
*/
getAll() {
return this.inner.getHeaders();
}
/**
* Appends a new header, without removing an old one with the same name.
*/
append(name, value) {
let oldValue = this.inner.getHeader(name);
if (oldValue === undefined) {
oldValue = [];
}
if (!Array.isArray(oldValue)) {
oldValue = [oldValue.toString()];
}
this.inner.setHeader(name, oldValue.concat(value));
}
}

@@ -67,2 +95,8 @@ class NodeResponse {

/**
* Updates the HTTP status code for this response.
*/
set status(value) {
this.inner.statusCode = value;
}
/**
* Returns the value of the Content-Type header, with any additional

@@ -75,6 +109,49 @@ * parameters such as charset= removed.

const type = this.headers.get('content-type');
if (!type)
if (!type) {
return '';
}
return type.split(';')[0];
}
/**
* Sends an informational response before the real response.
*
* This can be used to for example send a `100 Continue` or `103 Early Hints`
* response.
*/
async sendInformational(status, headers) {
let outHeaders = {};
if (typeof headers !== 'undefined') {
if (headers.getAll !== undefined) {
outHeaders = headers.getAll();
}
else {
outHeaders = headers;
}
}
/**
* It's a HTTP2 connection.
*/
if (node_http_utils_1.isHttp2Response(this.inner)) {
this.inner.stream.additionalHeaders(Object.assign({ ':status': status }, outHeaders));
}
else {
const rawHeaders = [];
for (const headerName of Object.keys(outHeaders)) {
const headerValue = outHeaders[headerName];
if (Array.isArray(headerValue)) {
for (const headerVal of headerValue) {
rawHeaders.push(`${headerName}: ${headerVal}\r\n`);
}
}
else {
rawHeaders.push(`${headerName}: ${headerValue}\r\n`);
}
}
// @ts-ignore _writeRaw is private but its the only sane way to access
// it.
const writeRaw = util_1.promisify(this.inner._writeRaw.bind(this.inner));
const message = `HTTP/1.1 ${status} ${http_1.default.STATUS_CODES[status]}\r\n${rawHeaders.join('')}\r\n`;
await writeRaw(message, 'ascii');
}
}
}

@@ -81,0 +158,0 @@ exports.NodeResponse = NodeResponse;

@@ -1,2 +0,2 @@

import { HeadersInterface } from './headers';
import { HeadersInterface, HeadersObject } from './headers';
/**

@@ -25,3 +25,4 @@ * This interface represents an incoming server request.

readonly type: string;
sendInformational: (status: number, headers?: HeadersInterface | HeadersObject) => Promise<void>;
}
export default Response;
{
"name": "curveball",
"version": "0.2.0",
"version": "0.3.0",
"description": "Curveball is a framework writting in Typescript for Node.js",

@@ -8,3 +8,3 @@ "main": "dist/index.js",

"prepublish": "make build",
"test": "make test",
"test": "make lint test",
"tsc": "tsc"

@@ -39,4 +39,5 @@ },

"@types/mocha": "^5.2.3",
"@types/node": "^10.3.5",
"@types/node": "^10.3.6",
"@types/node-fetch": "^2.1.1",
"@types/sinon": "^5.0.1",
"chai": "^4.1.2",

@@ -46,3 +47,5 @@ "mocha": "^5.2.0",

"nyc": "^12.0.2",
"sinon": "^6.0.1",
"ts-node": "^7.0.0",
"tslint": "^5.10.0",
"typescript": "^2.9.2"

@@ -49,0 +52,0 @@ },

@@ -16,10 +16,145 @@ Curveball

Status
------
If you used Koa in the past, this is going to look pretty familiar. I'm a big
fan of Koa myself and would recommend it over this project if you don't need
any of the things this project offers.
Literally nothing works at the moment.
Installation
------------
npm install curveball
Getting started
---------------
Curveball only provides a basic framework. Using it means implementing or
using curveball middleware. For example, if you want a router, use or build
a Router middleware.
All of the following examples are written in typescript, but it is also
possible to use the framework with plain javascript.
```typescript
import { Application, Context } from 'curveball';
const app = new Application();
app.use((ctx: Context) => {
ctx.response.status = 200;
ctx.body = 'Hello world!'
});
```
Sending 1xx Informational responses
-----------------------------------
Curveball has native support for sending informational responses. Examples are:
* [`100 Continue`][http-100] to let a client know even before the request
completed that it makes sense to continue, or that it should break off the
request.
* [`102 Processing`][http-102] to periodically indicate that the server is
still working on the response. This might not be very useful anymore.
* [`103 Early Hints`][http-103] a new standard to let a client or proxy know
early in the process that some headers might be coming, allowing clients or
proxies to for example pre-fetch certain resources even before the initial
request completes.
Here's an example of a middleware using `103 Early Hints`:
```typescript
import { Application, Context, Middleware } from 'curveball';
const app = new Curveball();
app.use(async (ctx: Context, next: Middleware) => {
await ctx.response.sendInformational(103, {
'Link' : [
'</style.css> rel="prefetch" as="style"',
'</script.js> rel="prefetch" as="script"',
]
});
await next();
});
```
API
---
### The Context class
The Context object has the following properties:
* `request` - An instance of `Request`.
* `response` - An instance of `Response`.
* `state` - An object you can use to store request-specific state information.
this object can be used to pass information between middlewares. A common
example is that an authentication middlware might set 'currently logged in
user' information here.
### The Request interface
The Request interface represents the HTTP request. It has the following
properties and methods:
* `headers` - An instance of `Headers`.
* `path` - The path of the request, for example `/foo.html`.
* `method` - For example, `POST`.
* `requestTarget` - The full `requestTarget` from the first line of the HTTP
request.
* `body` - This might represent the body, but is initially just empty. It's
up to middlewares to do something with raw body and parse it.
* `rawBody()` - This function uses the [raw-body][5] function to parse the
body from the request into a string or Buffer. You can only do this once,
so a middleware should use this function to populate `body`.
* `query` - An object containing the query parametes.
* `type` - The `Content-Type` without additional parameters.
* `accepts` - Uses the [accepts][6] package to do content-negotiation.
### The Response interface
The Response interface represents a HTTP response. It has the following
properties and methods:
* `headers` - An instance of `Headers`.
* `status` - The HTTP status code, for example `200` or `404`.
* `body` - The response body. Can be a string, a buffer or an Object. If it's
an object, the server will serialize it as JSON.
* `type` - The `Content-Type` without additional parameters.
* `sendInformational(status, headers?)` - Sends a `100 Continue`,
`102 Processing` or `103 Early Hints` response with optional headers.
### The Headers inteface
The Headers interface represents HTTP headers for both the `Request` and
`Response`.
It has the following methods:
* `set(name, value)` - Sets a HTTP header.
* `get(name)` - Returns the value of a HTTP header, or null.
* `delete(name)` - Deletes a HTTP header.
* `append(name, value)` - Adds a HTTP header, but doesn't erase an existing
one with the same name.
* `getAll()` - Returns all HTTP headers as a key-value object.
### Status
* Basic framework is in place.
* Many features still missing.
[1]: https://expressjs.com/ "Express"
[2]: https://koajs.com/ "Koa"
[3]: https://www.typescriptlang.org/ "TypeScript"
[4]: https://tools.ietf.org/html/rfc8297 "RFC8297"
[5]: https://www.npmjs.com/package/raw-body
[6]: https://www.npmjs.com/package/accepts
[http-100]: https://tools.ietf.org/html/rfc7231#section-6.2.1 "RFC7231: 100 Continue"
[http-102]: https://tools.ietf.org/html/rfc2518#section-10.1 "RFC2518: 102 Processing"
[http-103]: https://tools.ietf.org/html/rfc8297 "RFC8297: 103 Early Hints"

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc