Files
pnpm/packages/server/src/createServer.ts
2019-10-06 13:24:38 +03:00

207 lines
6.6 KiB
TypeScript

import { globalInfo } from '@pnpm/logger'
import {
RequestPackageOptions,
Resolution,
StoreController,
WantedDependency,
} from '@pnpm/store-controller-types'
import http = require('http')
import { IncomingMessage, Server, ServerResponse } from 'http'
import locking from './lock'
interface RequestBody {
msgId: string,
wantedDependency: WantedDependency,
options: RequestPackageOptions,
prefix: string,
opts: {
addDependencies: string[];
removeDependencies: string[];
prune: boolean;
},
storePath: string,
id: string,
searchQueries: string[],
}
export default function (
store: StoreController,
opts: {
path?: string,
port?: number,
hostname?: string,
ignoreStopRequests?: boolean,
ignoreUploadRequests?: boolean,
},
) {
const rawManifestPromises = {}
const filesPromises = {}
const lock = locking<void>()
const server = http.createServer(async (req: IncomingMessage, res: ServerResponse) => {
if (req.method !== 'POST') {
res.statusCode = 405 // Method Not Allowed
const responseError = { error: `Only POST is allowed, received ${req.method}` }
res.setHeader('Allow', 'POST')
res.end(JSON.stringify(responseError))
return
}
const bodyPromise = new Promise<RequestBody>((resolve, reject) => {
let body: any = '' // tslint:disable-line
req.on('data', (data) => {
body += data
})
req.on('end', async () => {
try {
if (body.length > 0) {
body = JSON.parse(body)
} else {
body = {}
}
resolve(body)
} catch (e) {
reject(e)
}
})
})
try {
let body: RequestBody
switch (req.url) {
case '/requestPackage': {
try {
body = await bodyPromise
const pkgResponse = await store.requestPackage(body.wantedDependency, body.options)
if (pkgResponse['bundledManifest']) { // tslint:disable-line
rawManifestPromises[body.msgId] = pkgResponse['bundledManifest'] // tslint:disable-line
pkgResponse.body['fetchingBundledManifestInProgress'] = true // tslint:disable-line
}
if (pkgResponse['files']) { // tslint:disable-line
filesPromises[body.msgId] = pkgResponse['files'] // tslint:disable-line
}
res.end(JSON.stringify(pkgResponse.body))
} catch (err) {
res.end(JSON.stringify({
error: {
message: err.message,
...JSON.parse(JSON.stringify(err)),
},
}))
}
break
}
case '/fetchPackage': {
try {
body = await bodyPromise
const pkgResponse = store.fetchPackage(body.options as RequestPackageOptions & {force: boolean, pkgId: string, resolution: Resolution})
if (pkgResponse['bundledManifest']) { // tslint:disable-line
rawManifestPromises[body.msgId] = pkgResponse['bundledManifest'] // tslint:disable-line
}
if (pkgResponse['files']) { // tslint:disable-line
filesPromises[body.msgId] = pkgResponse['files'] // tslint:disable-line
}
res.end(JSON.stringify({ inStoreLocation: pkgResponse.inStoreLocation }))
} catch (err) {
res.end(JSON.stringify({
error: {
message: err.message,
...JSON.parse(JSON.stringify(err)),
},
}))
}
break
}
case '/packageFilesResponse':
body = await bodyPromise
const filesResponse = await filesPromises[body.msgId]()
delete filesPromises[body.msgId]
res.end(JSON.stringify(filesResponse))
break
case '/rawManifestResponse':
body = await bodyPromise
const manifestResponse = await rawManifestPromises[body.msgId]()
delete rawManifestPromises[body.msgId]
res.end(JSON.stringify(manifestResponse))
break
case '/updateConnections':
body = await bodyPromise
await store.updateConnections(body.prefix, body.opts)
res.end(JSON.stringify('OK'))
break
case '/prune':
// Disable store pruning when a server is running
res.statusCode = 403
res.end()
break
case '/saveState':
await store.saveState()
res.end(JSON.stringify('OK'))
break
case '/importPackage':
const importPackageBody = (await bodyPromise) as any // tslint:disable-line:no-any
await store.importPackage(importPackageBody.from, importPackageBody.to, importPackageBody.opts)
res.end(JSON.stringify('OK'))
break
case '/upload':
// Do not return an error status code, just ignore the upload request entirely
if (opts.ignoreUploadRequests) {
res.statusCode = 403
res.end()
break
}
const uploadBody = (await bodyPromise) as any // tslint:disable-line:no-any
await lock(uploadBody.builtPkgLocation, () => store.upload(uploadBody.builtPkgLocation, uploadBody.opts))
res.end(JSON.stringify('OK'))
break
case '/stop':
if (opts.ignoreStopRequests) {
res.statusCode = 403
res.end()
break
}
globalInfo('Got request to stop the server')
await close()
res.end(JSON.stringify('OK'))
globalInfo('Server stopped')
break
case '/getPackageLocation': {
const { packageId, packageName, opts } = (await bodyPromise) as any // tslint:disable-line:no-any
const pkgLocation = await store.getPackageLocation(packageId, packageName, opts)
res.end(JSON.stringify(pkgLocation))
break
}
case '/findPackageUsages':
body = await bodyPromise
res.end(JSON.stringify(await store.findPackageUsages(body.searchQueries)))
break
default:
res.statusCode = 404
const error = { error: `${req.url} does not match any route` }
res.end(JSON.stringify(error))
}
} catch (e) {
res.statusCode = 503
const jsonErr = JSON.parse(JSON.stringify(e))
jsonErr.message = e.message
res.end(JSON.stringify(jsonErr))
}
})
let listener: Server
if (opts.path) {
listener = server.listen(opts.path)
} else {
listener = server.listen(opts.port, opts.hostname)
}
return { close }
function close () {
listener.close()
return store.close()
}
}