Initial working state

This commit is contained in:
Daniel Bulant 2020-07-15 19:47:30 +02:00
commit 5bda28719c
3 changed files with 249 additions and 0 deletions

68
cgi.ts Normal file
View 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
View 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
View 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;
}
}