Skip to content

Commit b79bb0c

Browse files
authored
[skip/server] Add option to disable CORS. (#779)
Closes #778.
2 parents 5efee93 + 2863474 commit b79bb0c

5 files changed

Lines changed: 138 additions & 18 deletions

File tree

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
{
2+
"$schema": "https://json.schemastore.org/mocharc.json",
3+
"require": "tsx"
4+
}

skipruntime-ts/server/package.json

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,19 +3,24 @@
33
"version": "0.0.11",
44
"type": "module",
55
"exports": {
6-
".": "./dist/server.js"
6+
".": "./dist/src/server.js"
77
},
88
"scripts": {
99
"build": "tsc",
1010
"clean": "rm -rf dist",
11-
"lint": "eslint"
11+
"lint": "eslint",
12+
"test": "mocha test/**/*.spec.ts"
1213
},
1314
"devDependencies": {
14-
"@types/express": "^5.0.0"
15+
"@types/chai": "^5.0.1",
16+
"@types/express": "^5.0.0",
17+
"chai": "^5.2.0",
18+
"mocha": "^11.1.0",
19+
"tsx": "^4.19.3"
1520
},
1621
"dependencies": {
17-
"express": "^4.21.1",
18-
"@skipruntime/core": "0.0.11"
22+
"@skipruntime/core": "0.0.11",
23+
"express": "^4.21.1"
1924
},
2025
"optionalDependencies": {
2126
"@skipruntime/native": "0.0.11",

skipruntime-ts/server/src/rest.ts

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,6 @@ export function controlService(service: ServiceInstance): express.Express {
109109

110110
export function streamingService(service: ServiceInstance): express.Express {
111111
const app = express();
112-
app.use(express.json());
113112

114113
app.get("/v1/streams/:uuid", (req, res) => {
115114
if (!req.accepts("text/event-stream")) {
@@ -120,11 +119,10 @@ export function streamingService(service: ServiceInstance): express.Express {
120119
const uuid = req.params.uuid;
121120
const subscriptionID = service.subscribe(uuid, {
122121
subscribed: () => {
123-
res.writeHead(200, {
124-
"Content-Type": "text/event-stream",
125-
Connection: "keep-alive",
126-
"Cache-Control": "no-cache",
127-
});
122+
res.set("Content-Type", "text/event-stream");
123+
res.set("Connection", "keep-alive");
124+
res.set("Cache-Control", "no-cache");
125+
res.status(200);
128126
res.flushHeaders();
129127
},
130128
notify: (update: CollectionUpdate<string, Json>) => {

skipruntime-ts/server/src/server.ts

Lines changed: 30 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,10 @@
44
* @packageDocumentation
55
*/
66

7-
import { type SkipService, ServiceInstance } from "@skipruntime/core";
7+
import { type SkipService } from "@skipruntime/core";
88
import { controlService, streamingService } from "./rest.js";
9+
import type { Express, Request, Response, NextFunction } from "express";
10+
import express from "express";
911

1012
/**
1113
* A running Skip server.
@@ -90,6 +92,7 @@ export type SkipServer = {
9092
* @param options.control_port - Port on which control service will listen.
9193
* @param options.streaming_port - Port on which streaming service will listen.
9294
* @param options.platform - Skip runtime platform to be used to run the service: either `wasm` (the default) or `native`.
95+
* @param options.no_cors - Disable CORS for the streaming endpoint.
9396
* @returns Object to manage the running server.
9497
*/
9598
export async function runService(
@@ -98,17 +101,18 @@ export async function runService(
98101
streaming_port: number;
99102
control_port: number;
100103
platform?: "wasm" | "native";
104+
no_cors?: boolean;
101105
} = {
102106
streaming_port: 8080,
103107
control_port: 8081,
104108
platform: "wasm",
109+
no_cors: false,
105110
},
106111
): Promise<SkipServer> {
107-
let instance: ServiceInstance;
112+
let runtime;
108113
if (options.platform == "native") {
109114
try {
110-
const runtime = await import("@skipruntime/native");
111-
instance = await runtime.initService(service);
115+
runtime = await import("@skipruntime/native");
112116
} catch (e) {
113117
console.error(
114118
'Error loading Skip runtime for specified "native" platform: ',
@@ -117,15 +121,15 @@ export async function runService(
117121
}
118122
} else {
119123
try {
120-
const runtime = await import("@skipruntime/wasm");
121-
instance = await runtime.initService(service);
124+
runtime = await import("@skipruntime/wasm");
122125
} catch (e) {
123126
console.error(
124127
'Error loading Skip runtime for specified "wasm" platform: ',
125128
);
126129
throw e;
127130
}
128131
}
132+
const instance = await runtime.initService(service);
129133
const controlHttpServer = controlService(instance).listen(
130134
options.control_port,
131135
() => {
@@ -134,7 +138,13 @@ export async function runService(
134138
);
135139
},
136140
);
137-
const streamingHttpServer = streamingService(instance).listen(
141+
const wrapMiddleware = (app: Express) => {
142+
if (options.no_cors) {
143+
return express().use(no_cors).use(app);
144+
}
145+
return app;
146+
};
147+
const streamingHttpServer = wrapMiddleware(streamingService(instance)).listen(
138148
options.streaming_port,
139149
() => {
140150
console.log(
@@ -151,3 +161,16 @@ export async function runService(
151161
},
152162
};
153163
}
164+
165+
function no_cors(req: Request, res: Response, next: NextFunction) {
166+
res.header("Access-Control-Allow-Credentials", "true");
167+
res.header("Access-Control-Allow-Methods", "GET,OPTIONS");
168+
res.header("Access-Control-Allow-Origin", "*");
169+
if (req.method.toUpperCase() == "OPTIONS") {
170+
res.statusCode = 204;
171+
res.setHeader("Content-Length", "0");
172+
res.end();
173+
} else {
174+
next();
175+
}
176+
}
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
import { runService, type SkipServer } from "../src/server.js";
2+
import type { Context, EagerCollection, Resource } from "@skipruntime/core";
3+
import { expect } from "chai";
4+
5+
type Post = {
6+
author_id: number;
7+
title: string;
8+
url: string;
9+
body: string;
10+
date: number;
11+
};
12+
13+
type PostsResourceInputs = {
14+
posts: EagerCollection<number, Post>;
15+
};
16+
17+
class PostsResource implements Resource<PostsResourceInputs> {
18+
instantiate(collections: PostsResourceInputs): EagerCollection<number, Post> {
19+
return collections.posts;
20+
}
21+
}
22+
23+
describe("runService({ no_cors: true })", function () {
24+
let service: SkipServer;
25+
before(async function () {
26+
service = await runService(
27+
{
28+
initialData: { posts: [] },
29+
resources: { posts: PostsResource },
30+
createGraph(
31+
inputs: { posts: EagerCollection<number, Post> },
32+
_context: Context,
33+
): PostsResourceInputs {
34+
return {
35+
posts: inputs.posts,
36+
};
37+
},
38+
},
39+
{
40+
control_port: 8081,
41+
streaming_port: 8080,
42+
no_cors: true,
43+
},
44+
);
45+
});
46+
after(async function () {
47+
await service.close();
48+
});
49+
50+
try {
51+
it("should set Access-Control-Allow-Credentials", async function () {
52+
const uuid = await (
53+
await fetch("http://localhost:8081/v1/streams/posts", {
54+
method: "POST",
55+
})
56+
).text();
57+
const resp = await fetch(`http://localhost:8080/v1/streams/${uuid}`, {
58+
headers: {
59+
Accept: "text/event-stream",
60+
},
61+
});
62+
expect(resp.headers.get("Access-Control-Allow-Credentials")).to.equal(
63+
"true",
64+
);
65+
});
66+
67+
it("should set Access-Control-Allow-Origin", async function () {
68+
const uuid = await (
69+
await fetch("http://localhost:8081/v1/streams/posts", {
70+
method: "POST",
71+
})
72+
).text();
73+
const resp = await fetch(`http://localhost:8080/v1/streams/${uuid}`, {
74+
headers: {
75+
Accept: "text/event-stream",
76+
},
77+
});
78+
expect(resp.headers.get("Access-Control-Allow-Origin")).to.equal("*");
79+
});
80+
81+
it("should respond to preflight requests", async function () {
82+
const resp = await fetch(`http://localhost:8080/v1/streams`, {
83+
method: "OPTIONS",
84+
});
85+
expect(resp.headers.get("Access-Control-Allow-Origin")).to.equal("*");
86+
});
87+
} catch (e) {
88+
console.log(e);
89+
}
90+
});

0 commit comments

Comments
 (0)