mirror of
https://github.com/danbulant/dejs-fcgi
synced 2026-05-19 03:58:32 +00:00
Initial working state
This commit is contained in:
commit
5bda28719c
3 changed files with 249 additions and 0 deletions
68
cgi.ts
Normal file
68
cgi.ts
Normal file
|
|
@ -0,0 +1,68 @@
|
|||
#!/home/dan/.deno/bin/deno run
|
||||
|
||||
import FCGI from "./fcgi.ts";
|
||||
import Record, { RecordTypes } from "./records/index.ts";
|
||||
import * as dejs from 'https://deno.land/x/dejs/mod.ts';
|
||||
|
||||
const server = Deno.listen({ port: 8990 });
|
||||
|
||||
const te = new TextEncoder;
|
||||
|
||||
function concatTypedArrays(a: any, b: any) { // a, b TypedArray of same type
|
||||
var c = new (a.constructor)(a.length + b.length);
|
||||
c.set(a, 0);
|
||||
c.set(b, a.length);
|
||||
return c;
|
||||
}
|
||||
|
||||
for await(const con of server) {
|
||||
var response = new Uint8Array(0);
|
||||
|
||||
var data: {
|
||||
records: Record[],
|
||||
done: boolean
|
||||
} = {
|
||||
records: [],
|
||||
done: false
|
||||
};
|
||||
|
||||
for await(var chunk of Deno.iter(con)) {
|
||||
response = concatTypedArrays(response, chunk);
|
||||
data = FCGI.parse(response);
|
||||
if(data.done) break;
|
||||
}
|
||||
|
||||
if(!(data.done)) continue;
|
||||
|
||||
var params = FCGI.parseNameValue(FCGI.getFullStream(data.records.filter(val => val.type === RecordTypes.PARAMS)));
|
||||
|
||||
var stdin = FCGI.getFullStream(data.records.filter(val => val.type === RecordTypes.STDIN));
|
||||
// var inputData = FCGI.getFullStream(data.records.filter(val => val.type === RecordTypes.DATA));
|
||||
|
||||
var status = 200;
|
||||
var headers = new Map<string, string>();
|
||||
headers.set("Content-type", "text/html");
|
||||
headers.set("X-powered-by", "Dejs-FCGI");
|
||||
var dataOutput = await dejs.renderFileToString(params.get("SCRIPT_FILENAME")!, {
|
||||
PARAMS: params,
|
||||
STDIN: stdin,
|
||||
|
||||
setStatus(statusNumber: number) {
|
||||
status = statusNumber;
|
||||
},
|
||||
|
||||
setHeader(header: string, value: string) {
|
||||
headers.set(header, value);
|
||||
}
|
||||
});
|
||||
|
||||
var output = `${params.get("SERVER_PROTOCOL")!} ${status}\r\n${Array.from(headers).map(([a, b]) => `${a}: ${b}`).join("\r\n")}\r\n\r\n${dataOutput}`;
|
||||
|
||||
var responseData = FCGI.write(te.encode(output),1);
|
||||
|
||||
for(var buf of responseData.toBufferArray()) {
|
||||
await con.write(buf);
|
||||
}
|
||||
|
||||
con.close();
|
||||
}
|
||||
101
fcgi.ts
Normal file
101
fcgi.ts
Normal file
|
|
@ -0,0 +1,101 @@
|
|||
import Record, {RecordTypes} from "./records/index.ts";
|
||||
const td = new TextDecoder;
|
||||
|
||||
function concatTypedArrays(a: any, b: any) { // a, b TypedArray of same type
|
||||
var c = new (a.constructor)(a.length + b.length);
|
||||
c.set(a, 0);
|
||||
c.set(b, a.length);
|
||||
return c;
|
||||
}
|
||||
|
||||
export default class FCGI {
|
||||
static FCGI_HEADER_LEN = 8;
|
||||
static FCGI_VERSION = 1;
|
||||
|
||||
static FCGI_KEEP_CONN = 1;
|
||||
static FCGI_RESPONDER = 1;
|
||||
|
||||
static parse(buffer: Uint8Array): {
|
||||
records: Record[],
|
||||
done: boolean
|
||||
} {
|
||||
var done = false;
|
||||
var records = [];
|
||||
while(buffer.length) {
|
||||
if(buffer[0] !== 1) throw new Error("Unsupported version" + buffer[0]);
|
||||
var type = buffer[1];
|
||||
var requestId = (buffer[2] << 8) + buffer[3];
|
||||
var contentLength = (buffer[4] << 8) + buffer[5];
|
||||
var paddingLength = buffer[6];
|
||||
// 7 reserved
|
||||
var contentData = buffer.slice(8, 8 + contentLength);
|
||||
|
||||
records.push(new Record({
|
||||
type,
|
||||
requestId,
|
||||
contentLength,
|
||||
paddingLength,
|
||||
contentData
|
||||
}));
|
||||
|
||||
if(type === RecordTypes.STDIN && contentLength === 0) done = true; // now app can respond
|
||||
|
||||
buffer = buffer.slice(8 + contentLength + paddingLength);
|
||||
}
|
||||
return {
|
||||
records,
|
||||
done
|
||||
};
|
||||
}
|
||||
|
||||
static write(buffer: Uint8Array, rid: number, error: boolean = false) {
|
||||
return new Record({
|
||||
requestId: rid,
|
||||
contentLength: buffer.length,
|
||||
paddingLength: 0,
|
||||
contentData: buffer,
|
||||
type: error ? RecordTypes.STDERR : RecordTypes.STDOUT
|
||||
});
|
||||
}
|
||||
|
||||
static getFullStream(records: Record[]): Uint8Array {
|
||||
var content;
|
||||
for(var record of records) {
|
||||
if(content) {
|
||||
concatTypedArrays(content, record.contentData);
|
||||
} else {
|
||||
content = record.contentData;
|
||||
}
|
||||
}
|
||||
|
||||
return content;
|
||||
}
|
||||
|
||||
static parseNameValue(content: Uint8Array) {
|
||||
var data = new Map<string, string>();
|
||||
|
||||
while(content.length) {
|
||||
var nameLength = content[0];
|
||||
if(nameLength >> 7) {
|
||||
nameLength += ((content[3] & 0x7f) << 24) + (content[2] << 16) + (content[1] << 8);
|
||||
content = content.slice(4);
|
||||
} else {
|
||||
content = content.slice(1);
|
||||
}
|
||||
var valueLength = content[0];
|
||||
if(valueLength >> 7) {
|
||||
valueLength += ((content[3] & 0x7f) << 24) + (content[2] << 16) + (content[1] << 8);
|
||||
content = content.slice(4);
|
||||
} else {
|
||||
content = content.slice(1);
|
||||
}
|
||||
var nameData = content.slice(0, nameLength);
|
||||
var valueData = content.slice(nameLength, nameLength + valueLength);
|
||||
data.set(td.decode(nameData), td.decode(valueData));
|
||||
|
||||
content = content.slice(nameLength + valueLength);
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
}
|
||||
80
records/index.ts
Normal file
80
records/index.ts
Normal file
|
|
@ -0,0 +1,80 @@
|
|||
|
||||
export enum RecordTypes {
|
||||
BEGIN_REQUEST = 1,
|
||||
ABORT_REQUEST,
|
||||
END_REQUEST,
|
||||
PARAMS,
|
||||
STDIN,
|
||||
STDOUT,
|
||||
STDERR,
|
||||
DATA,
|
||||
GET_VALUES,
|
||||
GET_VALUES_RESULT,
|
||||
UNKNOWN_TYPE
|
||||
}
|
||||
|
||||
|
||||
export default class Record {
|
||||
//1 byte
|
||||
version: 1 = 1;
|
||||
|
||||
//1 byte
|
||||
type: RecordTypes;
|
||||
|
||||
//2 bytes
|
||||
requestId: number;
|
||||
|
||||
//2bytes
|
||||
contentLength: number;
|
||||
//1 byte
|
||||
paddingLength: number;
|
||||
|
||||
//contentLength bytes;
|
||||
contentData: any;
|
||||
|
||||
constructor(data: {
|
||||
version?: 1,
|
||||
type: RecordTypes,
|
||||
requestId: number,
|
||||
contentLength: number,
|
||||
paddingLength: number,
|
||||
contentData: any
|
||||
}) {
|
||||
this.type = data.type;
|
||||
this.requestId = data.requestId;
|
||||
this.contentLength = data.contentLength;
|
||||
this.paddingLength = data.paddingLength;
|
||||
this.contentData = data.contentData;
|
||||
}
|
||||
|
||||
get isManagement() {
|
||||
return this.requestId === 0;
|
||||
}
|
||||
|
||||
toBufferArray(): Uint8Array[] {
|
||||
var bufArray = [];
|
||||
var content = this.contentData;
|
||||
|
||||
while(true) {
|
||||
var small = content.slice(0, 65535);
|
||||
var buffer = new Uint8Array(8 + this.contentLength);
|
||||
buffer.set([
|
||||
this.version,
|
||||
this.type,
|
||||
(this.requestId >> 8) & 0xFF,
|
||||
this.requestId & 0xFF,
|
||||
(small.length >> 8) & 0xFF,
|
||||
small.length & 0xFF,
|
||||
0,
|
||||
0
|
||||
]);
|
||||
buffer.set(small, 8);
|
||||
bufArray.push(buffer);
|
||||
|
||||
content = content.slice(65535, content.length);
|
||||
|
||||
if(!content.slice(65535, content.length).length) break;
|
||||
}
|
||||
return bufArray;
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue