Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion .github/workflows/npm-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,9 @@ jobs:
- uses: actions/setup-node@v3
with:
node-version: 21
- run: npm test
- uses: oven-sh/setup-bun@v1
with:
bun-version: 1.1.4
- run: node --test **/test/**/[!b][!u][!n]*/**/*.js
- run: bun test **/test/**/bun/**/**

128 changes: 116 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
# objective-http
Proxy classes for creating a http server


## Server

There are all ```Server``` classes feature.
Your endpoints must implement ```Endpoint``` class interface (```route``` and ```handle``` functions).
There are all `Server` classes feature.
Your endpoints must implement `Endpoint` class interface (`route()` and `async handle(request)` methods).

``` javascript
```javascript
const http = require('node:http');
const cluster = require('node:cluster');

Expand All @@ -28,15 +29,15 @@ const {
new ClusteredServer(
new LoggedServer(
new Server(
http,
new LoggedInputRequest(new JsonInputRequest(new InputRequest()), console),
new LoggedOutputResponse(new JsonOutputResponse(new OutputResponse()), console),
new Endpoints([
new MyFirstEndpoint(new LoggedEndpoint(new Endpoint(), console)),
new MySecondEndpoint(new LoggedEndpoint(new Endpoint(), console)),
new MyThirdEndpoint(new LoggedEndpoint(new Endpoint(), console))
]),
{port: server_port}
{port: server_port},
new LoggedInputRequest(new JsonInputRequest(new InputRequest()), console),
new LoggedOutputResponse(new JsonOutputResponse(new OutputResponse()), console),
http
),
console
),
Expand All @@ -45,19 +46,21 @@ new ClusteredServer(
).start();
```


## Client

``` javascript
const https = require('node:https');
```javascript
const http = require('node:http');
const requestFunction = http.request;
const {
OutputRequest,
InputResponse
} = require('objective-http').client;


const response = await new OutputRequest(
https,
new InputResponse(),
requestFunction,
{
url: 'https://example.com',
method: 'POST',
Expand All @@ -69,14 +72,115 @@ console.log(response.body().toString());

//or

const request = new OutputRequest(https, new InputResponse());
const request = new OutputRequest(new InputResponse(), requestFunction);

const otherResponse = await (request
.copy({
url: 'https://example.com',
method: 'POST',
body: 'test body'
}))
.send()
.send();

console.log(response.body().toString());
```


## [Bun](https://bun.sh) support

`server` and `client` packages support Bun by default.
But there ara special `bun` package with native [Bun API](https://bun.sh/docs/runtime/bun-apis) implementation (like `Bun.serve()`).
And you should replace `node:http` package with `objective-http.bun.bunttp` in your `Server` configuration.
[Don't use](https://bun.sh/docs/runtime/nodejs-apis#node-cluster) `ClusteredServer` with `Bun`!!!


### Server Bun usage

It should work with `node` and `bun`:

```javascript
const http = require('node:http');

const {
Server,
LoggedServer,
InputRequest,
JsonInputRequest,
LoggedInputRequest,
OutputResponse,
JsonOutputResponse,
LoggedOutputResponse,
Endpoint,
LoggedEndpoint,
Endpoints
} = require('objective-http').server;

new LoggedServer(
new Server(
new Endpoints([
new MyFirstEndpoint(new LoggedEndpoint(new Endpoint(), console)),
new MySecondEndpoint(new LoggedEndpoint(new Endpoint(), console)),
new MyThirdEndpoint(new LoggedEndpoint(new Endpoint(), console))
]),
{port: server_port},
new LoggedInputRequest(new JsonInputRequest(new InputRequest()), console),
new LoggedOutputResponse(new JsonOutputResponse(new OutputResponse()), console),
http
),
console
).start()
```

In order for the code to be executed only by `bun` (with `Bun API` inside), you need to make changes to the import block.
`bun` package redeclare only `InputRequest` and `OutputResponse` classes. Other classes taken from `server` package.

```javascript
const http = require('objective-http').bun.bunttp;

const {
Server,
LoggedServer,
InputRequest,
JsonInputRequest,
LoggedInputRequest,
OutputResponse,
JsonOutputResponse,
LoggedOutputResponse,
Endpoint,
LoggedEndpoint,
Endpoints
} = require('objective-http').bun.server;
```


### Client Bun usage

It should work with `node` and `bun`:

```javascript
const http = require('node:http');
const requestFunction = http.request;
const {
OutputRequest,
InputResponse
} = require('objective-http').client;

await (new OutputRequest(new InputResponse(), requestFunction)
.copy({
url: 'https://example.com',
method: 'POST',
body: 'test body'
}))
.send();
```

In order for the code to be executed only by `bun`, you need to make changes to the import block.

```javascript
const http = require('objective-http').bun.bunttp;
const requestFunction = http.request;
const {
OutputRequest,
InputResponse
} = require('objective-http').bun.client;
```
13 changes: 10 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,17 @@
"version": "master",
"private": "true",
"description": "Proxy classes for creating a http server",
"keywords": ["web", "web-server", "http", "http-server", "oop"],
"keywords": [
"web",
"web-server",
"http",
"http-server",
"oop"
],
"scripts": {
"prepareToPublish": "node dist/prepareToPublish.js",
"test": "node --test"
"test": "node --test **/test/**/[!b][!u][!n]*/**/*.js",
"testBun": "bun test **/test/**/bun/**/**"
},
"author": {
"name": "volatilization",
Expand All @@ -16,4 +23,4 @@
"url": "git+https://github.com/volatilization/objective-http.git"
},
"license": "LGPL-3.0-only"
}
}
29 changes: 29 additions & 0 deletions src/js/bun/Bunttp.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
module.exports = class Bunttp {
#serverConfig;
#server;

constructor(serverConfig = {}, server = {}) {
this.#serverConfig = serverConfig;
this.#server = server;
}

createServer(cb) {
this.#serverConfig.fetch = cb;
return this;
}

listen(options, cb) {
this.#serverConfig.port = options.port;
this.#server = Bun.serve(this.#serverConfig);
cb();
return this;
}

close(cb) {
this.#server.stop();
cb();
return this;
}

request = fetch;
}
4 changes: 4 additions & 0 deletions src/js/bun/client/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
module.exports = {
OutputRequest: require('./request/OutputRequest'),
InputResponse: require('./response/InputResponse')
}
26 changes: 26 additions & 0 deletions src/js/bun/client/request/OutputRequest.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
module.exports = class OutputRequest {
#response;
#requestFunction;
#options;

constructor(response, requestFunction, options) {
this.#response = response;
this.#requestFunction = requestFunction;
this.#options = options;
}

copy(options = this.#options, response = this.#response, requestFunction = this.#requestFunction) {
return new OutputRequest(response, requestFunction, options);
}

async send() {
try {
return await (this.#response
.copy(await this.#requestFunction(this.#options.url, this.#options)))
.flush()

} catch (e) {
throw new Error(e.message, {cause: 'INVALID_REQUEST'});
}
}
}
40 changes: 40 additions & 0 deletions src/js/bun/client/response/InputResponse.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
module.exports = class InputResponse {
#inputStream;
#options;

constructor(inputStream, options) {
this.#inputStream = inputStream;
this.#options = options;
}

copy(inputStream = this.#inputStream, options = this.#options) {
return new InputResponse(inputStream, options);
}

async flush() {
try {
return new InputResponse(this.#inputStream,
{
statusCode: this.#inputStream.status,
headers: this.#inputStream.headers,
body: Buffer.from(await (await this.#inputStream.blob()).arrayBuffer())
}
);

} catch (e) {
throw new Error(e.message, {cause: 'INVALID_RESPONSE'});
}
}

statusCode() {
return this.#options.statusCode;
}

headers() {
return this.#options.headers;
}

body() {
return this.#options.body;
}
}
5 changes: 5 additions & 0 deletions src/js/bun/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
module.exports = {
server: require('./server'),
client: require('./client'),
bunttp: new (require('./Bunttp'))()
}
4 changes: 4 additions & 0 deletions src/js/bun/server/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
module.exports = {
InputRequest: require('./request/InputRequest'),
OutputResponse: require('./response/OutputResponse')
}
50 changes: 50 additions & 0 deletions src/js/bun/server/request/InputRequest.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
module.exports = class InputRequest {
#inputStream;
#options;

constructor(inputStream, options) {
this.#inputStream = inputStream;
this.#options = options;
}

copy(inputStream = this.#inputStream, options = this.#options) {
return new InputRequest(inputStream, options);
}

async flush() {
try {
return new InputRequest(
this.#inputStream,
{
method: this.#inputStream.method,
path: new URL(this.#inputStream.url).pathname,
query: new URL(this.#inputStream.url).searchParams,
headers: this.#inputStream.headers,
body: Buffer.from(await (await this.#inputStream.blob()).arrayBuffer())
}
);

} catch (e) {
throw new Error(e.message, {cause: 'INVALID_REQUEST'});
}
}

route() {
return {
method: this.#options.method.toString().toUpperCase(),
path: this.#options.path.toString().toLowerCase()
}
}

query() {
return this.#options.query;
}

body() {
return this.#options.body;
}

headers() {
return this.#options.headers;
}
}
Loading