Use X-Original-Method for forcing a POST method to become a QUERY method.master
@@ -1,4 +1,5 @@ | |||||
import http, { createServer as httpCreateServer } from 'http'; | import http, { createServer as httpCreateServer } from 'http'; | ||||
import { HTTPParser } from 'http-parser-js'; | |||||
import { createServer as httpCreateSecureServer } from 'https'; | import { createServer as httpCreateSecureServer } from 'https'; | ||||
import {constants,} from 'http2'; | import {constants,} from 'http2'; | ||||
import * as v from 'valibot'; | import * as v from 'valibot'; | ||||
@@ -195,6 +196,7 @@ export const createServer = (backendState: BackendState, serverParams = {} as Cr | |||||
const isHttps = 'key' in serverParams && 'cert' in serverParams; | const isHttps = 'key' in serverParams && 'cert' in serverParams; | ||||
const theRes = new CqrsEventEmitter(); | const theRes = new CqrsEventEmitter(); | ||||
http.METHODS.push('QUERY'); | |||||
const server = isHttps | const server = isHttps | ||||
? httpCreateSecureServer({ | ? httpCreateSecureServer({ | ||||
key: serverParams.key, | key: serverParams.key, | ||||
@@ -780,6 +782,54 @@ export const createServer = (backendState: BackendState, serverParams = {} as Cr | |||||
)(reqRaw, res); | )(reqRaw, res); | ||||
}; | }; | ||||
// server.on('connection', (socket) => { | |||||
// if (!HTTPParser.methods.includes('QUERY')) { | |||||
// HTTPParser.methods.push('QUERY'); | |||||
// } | |||||
// const httpParserMut = (HTTPParser as unknown as Record<string, unknown>); | |||||
// console.log(httpParserMut.methods); | |||||
// httpParserMut.socket = socket; | |||||
// httpParserMut.remove = () => { | |||||
// // noop | |||||
// }; | |||||
// httpParserMut.free = () => { | |||||
// // noop | |||||
// }; | |||||
// socket.parser = httpParserMut; | |||||
// }); | |||||
// server.on('connection', (socket) => { | |||||
// let newLineOffset; | |||||
// let receiveBuffer = Buffer.from(''); | |||||
// const listeners = socket.listeners('data'); | |||||
// const oldListener = listeners[0]; | |||||
// | |||||
// function newListener(this: any, d, start, end) { | |||||
// console.log(d.slice(start, end).toString('utf-8')); | |||||
// | |||||
// receiveBuffer = Buffer.concat([receiveBuffer, d.slice(start, end)]); | |||||
// if ((newLineOffset = receiveBuffer.toString('ascii').indexOf('\n')) > -1) { | |||||
// var firstLineParts = receiveBuffer.slice(0, newLineOffset).toString().split(' '); | |||||
// firstLineParts[0] = firstLineParts[0].replace(/^QUERY$/ig, 'POST'); | |||||
// //firstLineParts[2] = firstLineParts[2].replace(/^ICE\//ig, 'HTTP/'); | |||||
// receiveBuffer = Buffer.concat([ | |||||
// Buffer.from( | |||||
// firstLineParts.join(' ') + '\r\n' + | |||||
// 'Content-Length: 9007199254740992\r\n' | |||||
// ), | |||||
// receiveBuffer.slice(newLineOffset + 1) | |||||
// ]); | |||||
// } | |||||
// | |||||
// console.log(receiveBuffer.toString('utf-8')); | |||||
// oldListener.apply(this, d); | |||||
// } | |||||
// | |||||
// socket.on('data', newListener); | |||||
// socket.off('data', oldListener); | |||||
// }); | |||||
// TODO create server directly from net.createConnection() | |||||
server.on('request', handleRequest); | server.on('request', handleRequest); | ||||
return { | return { | ||||
@@ -1,7 +1,22 @@ | |||||
import {RequestDecorator} from '../../../../common'; | import {RequestDecorator} from '../../../../common'; | ||||
const WHITELISTED_METHODS = [ | |||||
'QUERY' | |||||
] as const; | |||||
export const decorateRequestWithMethod: RequestDecorator = (req) => { | export const decorateRequestWithMethod: RequestDecorator = (req) => { | ||||
req.method = req.method?.trim().toUpperCase() ?? ''; | req.method = req.method?.trim().toUpperCase() ?? ''; | ||||
if (req.method === 'POST') { | |||||
const spoofedMethod = req.headers['x-original-method']; | |||||
if (Array.isArray(spoofedMethod)) { | |||||
return req; | |||||
} | |||||
const whitelistedMethod = WHITELISTED_METHODS.find((s) => s === spoofedMethod); | |||||
if (typeof whitelistedMethod === 'string') { | |||||
req.method = whitelistedMethod; | |||||
} | |||||
} | |||||
return req; | return req; | ||||
}; | }; |
@@ -6,6 +6,7 @@ export const isTextMediaType = (mediaType: string) => ( | |||||
|| [ | || [ | ||||
'application/json', | 'application/json', | ||||
'application/xml', | 'application/xml', | ||||
'application/x-www-form-urlencoded', | |||||
...PATCH_CONTENT_TYPES, | ...PATCH_CONTENT_TYPES, | ||||
].includes(mediaType) | ].includes(mediaType) | ||||
); | ); | ||||
@@ -105,7 +105,7 @@ describe('happy path', () => { | |||||
}); | }); | ||||
})); | })); | ||||
describe.skip('querying collections', () => { | |||||
describe('querying collections', () => { | |||||
beforeEach(() => { | beforeEach(() => { | ||||
vi | vi | ||||
.spyOn(DummyDataSource.prototype, 'getMultiple') | .spyOn(DummyDataSource.prototype, 'getMultiple') | ||||
@@ -121,11 +121,21 @@ describe('happy path', () => { | |||||
}); | }); | ||||
it('returns data', async () => { | it('returns data', async () => { | ||||
// const [res, resData] = await client({ | |||||
// method: 'QUERY', | |||||
// path: `${BASE_PATH}/pianos`, | |||||
// headers: { | |||||
// 'content-type': 'application/x-www-form-urlencoded', | |||||
// }, | |||||
// body: 'foo=bar', | |||||
// }); | |||||
const [res, resData] = await client({ | const [res, resData] = await client({ | ||||
method: 'QUERY', | |||||
method: 'POST', | |||||
path: `${BASE_PATH}/pianos`, | path: `${BASE_PATH}/pianos`, | ||||
headers: { | headers: { | ||||
'content-type': 'application/x-www-form-urlencoded', | 'content-type': 'application/x-www-form-urlencoded', | ||||
'x-original-method': 'QUERY', | |||||
}, | }, | ||||
body: 'foo=bar', | body: 'foo=bar', | ||||
}); | }); | ||||
@@ -31,6 +31,7 @@ export const createTestClient = (options: Omit<RequestOptions, 'method' | 'path' | |||||
const headers: OutgoingHttpHeaders = { | const headers: OutgoingHttpHeaders = { | ||||
...(options.headers ?? {}), | ...(options.headers ?? {}), | ||||
...etcAdditionalHeaders, | ...etcAdditionalHeaders, | ||||
...(params.headers ?? {}), | |||||
}; | }; | ||||
let contentTypeHeader: string | undefined; | let contentTypeHeader: string | undefined; | ||||