1
0
mirror of https://github.com/sasjs/server.git synced 2025-12-10 11:24:35 +00:00

Merge pull request #66 from sasjs/set-response-headers

Set response headers
This commit is contained in:
Muhammad Saad
2022-02-18 07:08:45 +04:00
committed by GitHub
11 changed files with 160 additions and 34 deletions

13
api/package-lock.json generated
View File

@@ -38,6 +38,7 @@
"@types/supertest": "^2.0.11",
"@types/swagger-ui-express": "^4.1.3",
"dotenv": "^10.0.0",
"http-headers-validation": "^0.0.1",
"jest": "^27.0.6",
"mongodb-memory-server": "^8.0.0",
"nodemon": "^2.0.7",
@@ -4651,6 +4652,12 @@
"node": ">= 0.6"
}
},
"node_modules/http-headers-validation": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/http-headers-validation/-/http-headers-validation-0.0.1.tgz",
"integrity": "sha1-0xUbTFjQjySTSnbKgYVIzPBa+3g=",
"dev": true
},
"node_modules/http-proxy-agent": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-4.0.1.tgz",
@@ -13747,6 +13754,12 @@
"toidentifier": "1.0.0"
}
},
"http-headers-validation": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/http-headers-validation/-/http-headers-validation-0.0.1.tgz",
"integrity": "sha1-0xUbTFjQjySTSnbKgYVIzPBa+3g=",
"dev": true
},
"http-proxy-agent": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-4.0.1.tgz",

View File

@@ -70,6 +70,7 @@
"@types/supertest": "^2.0.11",
"@types/swagger-ui-express": "^4.1.3",
"dotenv": "^10.0.0",
"http-headers-validation": "^0.0.1",
"jest": "^27.0.6",
"mongodb-memory-server": "^8.0.0",
"nodemon": "^2.0.7",

View File

@@ -26,11 +26,9 @@ if (MODE?.trim() !== 'server' || CORS?.trim() === 'enable') {
app.use(express.json({ limit: '50mb' }))
app.use(morgan('tiny'))
app.use(express.static(path.join(__dirname, '../public')))
app.use(express.static(getWebBuildFolderPath()))
app.use('/', webRouter)
app.use('/SASjsApi', apiRouter)
app.use(express.json({ limit: '50mb' }))
app.use(express.static(getWebBuildFolderPath()))
export default connectDB().then(() => app)

View File

@@ -1,6 +1,6 @@
import express from 'express'
import { Request, Security, Route, Tags, Post, Body } from 'tsoa'
import { ExecutionController } from './internal'
import { ExecuteReturnRaw, ExecutionController } from './internal'
import { PreProgramVars } from '../types'
interface ExecuteSASCodePayload {
@@ -30,15 +30,18 @@ export class CodeController {
const executeSASCode = async (req: any, { code }: ExecuteSASCodePayload) => {
try {
const result = await new ExecutionController().executeProgram(
code,
getPreProgramVariables(req),
{ ...req.query, _debug: 131 },
undefined,
true
)
const { result, httpHeaders } =
(await new ExecutionController().executeProgram(
code,
getPreProgramVariables(req),
{ ...req.query, _debug: 131 },
undefined,
true
)) as ExecuteReturnRaw
return result as string
req.res?.set(httpHeaders)
return result
} catch (err: any) {
throw {
code: 400,

View File

@@ -3,12 +3,28 @@ import fs from 'fs'
import { getSessionController } from './'
import { readFile, fileExists, createFile, moveFile } from '@sasjs/utils'
import { PreProgramVars, TreeNode } from '../../types'
import { generateFileUploadSasCode, getTmpFilesFolderPath } from '../../utils'
import {
extractHeaders,
generateFileUploadSasCode,
getTmpFilesFolderPath,
HTTPHeaders
} from '../../utils'
export interface ExecutionVars {
[key: string]: string | number | undefined
}
export interface ExecuteReturnRaw {
httpHeaders: HTTPHeaders
result: string
}
export interface ExecuteReturnJson {
httpHeaders: HTTPHeaders
webout: string
log?: string
}
export class ExecutionController {
async executeFile(
programPath: string,
@@ -30,13 +46,14 @@ export class ExecutionController {
returnJson
)
}
async executeProgram(
program: string,
preProgramVariables: PreProgramVars,
vars: ExecutionVars,
otherArgs?: any,
returnJson?: boolean
) {
): Promise<ExecuteReturnRaw | ExecuteReturnJson> {
const sessionController = getSessionController()
const session = await sessionController.getSession()
@@ -44,11 +61,11 @@ export class ExecutionController {
session.consumed = true
const logPath = path.join(session.path, 'log.log')
const headersPath = path.join(session.path, 'stpsrv_header.txt')
const weboutPath = path.join(session.path, 'webout.txt')
await createFile(weboutPath, '')
const tokenFile = path.join(session.path, 'accessToken.txt')
await createFile(weboutPath, '')
await createFile(
tokenFile,
preProgramVariables?.accessToken ?? 'accessToken'
@@ -124,6 +141,12 @@ ${program}`
const webout = (await fileExists(weboutPath))
? await readFile(weboutPath)
: ''
const headersContent = (await fileExists(headersPath))
? await readFile(headersPath)
: ''
const httpHeaders: HTTPHeaders = headersContent
? extractHeaders(headersContent)
: {}
const debugValue =
typeof vars._debug === 'string' ? parseInt(vars._debug) : vars._debug
@@ -133,15 +156,20 @@ ${program}`
if (returnJson) {
return {
httpHeaders,
webout,
log:
(debugValue && debugValue >= 131) || session.crashed ? log : undefined
}
}
return (debugValue && debugValue >= 131) || session.crashed
? `<html><body>${webout}<div style="text-align:left"><hr /><h2>SAS Log</h2><pre>${log}</pre></div></body></html>`
: webout
return {
httpHeaders,
result:
(debugValue && debugValue >= 131) || session.crashed
? `<html><body>${webout}<div style="text-align:left"><hr /><h2>SAS Log</h2><pre>${log}</pre></div></body></html>`
: webout
}
}
buildDirectoryTree() {

View File

@@ -1,7 +1,12 @@
import express from 'express'
import path from 'path'
import { Request, Security, Route, Tags, Post, Body, Get, Query } from 'tsoa'
import { ExecutionController, ExecutionVars } from './internal'
import {
ExecuteReturnJson,
ExecuteReturnRaw,
ExecutionController,
ExecutionVars
} from './internal'
import { PreProgramVars } from '../types'
import { getTmpFilesFolderPath, makeFilesNamesMap } from '../utils'
@@ -73,11 +78,14 @@ const executeReturnRaw = async (
.replace(new RegExp('/', 'g'), path.sep) + '.sas'
try {
const result = await new ExecutionController().executeFile(
sasCodePath,
getPreProgramVariables(req),
query
)
const { result, httpHeaders } =
(await new ExecutionController().executeFile(
sasCodePath,
getPreProgramVariables(req),
query
)) as ExecuteReturnRaw
req.res?.set(httpHeaders)
return result as string
} catch (err: any) {
@@ -102,16 +110,20 @@ const executeReturnJson = async (
const filesNamesMap = req.files?.length ? makeFilesNamesMap(req.files) : null
try {
const { webout, log } = (await new ExecutionController().executeFile(
sasCodePath,
getPreProgramVariables(req),
{ ...req.query, ...req.body },
{ filesNamesMap: filesNamesMap },
true
)) as { webout: string; log: string }
const { webout, log, httpHeaders } =
(await new ExecutionController().executeFile(
sasCodePath,
getPreProgramVariables(req),
{ ...req.query, ...req.body },
{ filesNamesMap: filesNamesMap },
true
)) as ExecuteReturnJson
req.res?.set(httpHeaders)
return {
status: 'success',
_webout: webout,
_webout: webout as string,
log
}
} catch (err: any) {

View File

@@ -7,6 +7,8 @@ appPromise.then(async (app) => {
const protocol = process.env.PROTOCOL ?? 'http'
const sasJsPort = process.env.PORT ?? 5000
console.log('PROTOCOL: ', protocol)
if (protocol !== 'https') {
app.listen(sasJsPort, () => {
console.log(

View File

@@ -0,0 +1,25 @@
const headerUtils = require('http-headers-validation')
export interface HTTPHeaders {
[key: string]: string | undefined
}
export const extractHeaders = (content: string): HTTPHeaders => {
const headersObj: HTTPHeaders = {}
const headersArr = content
.split('\n')
.map((line) => line.trim())
.filter((line) => !!line)
headersArr.forEach((headerStr) => {
const [key, value] = headerStr.split(':').map((data) => data.trim())
if (value && headerUtils.validateHeader(key, value)) {
headersObj[key] = value
} else {
delete headersObj[key]
}
})
return headersObj
}

View File

@@ -7,6 +7,9 @@ export const getCertificates = async () => {
const keyPath = PRIVATE_KEY ?? (await getFileInput('Private Key (PEM)'))
const certPath = FULL_CHAIN ?? (await getFileInput('Full Chain (PEM)'))
console.log('keyPath: ', keyPath)
console.log('certPath: ', certPath)
const key = await readFile(keyPath)
const cert = await readFile(certPath)

View File

@@ -1,4 +1,5 @@
export * from './connectDB'
export * from './extractHeaders'
export * from './file'
export * from './generateAccessToken'
export * from './generateAuthCode'

View File

@@ -0,0 +1,40 @@
import { extractHeaders } from '..'
describe('extractHeaders', () => {
it('should return valid http headers', () => {
const headers = extractHeaders(`
Content-type: application/csv
Cache-Control: public, max-age=2000
Content-type: application/text
Cache-Control: public, max-age=1500
Content-type: application/zip
Cache-Control: public, max-age=1000
`)
expect(headers).toEqual({
'Content-type': 'application/zip',
'Cache-Control': 'public, max-age=1000'
})
})
it('should not return http headers if last occurrence is blank', () => {
const headers = extractHeaders(`
Content-type: application/csv
Cache-Control: public, max-age=1000
Content-type: application/text
Content-type:
`)
expect(headers).toEqual({ 'Cache-Control': 'public, max-age=1000' })
})
it('should return only valid http headers', () => {
const headers = extractHeaders(`
Content-type[]: application/csv
Content//-type: application/text
Content()-type: application/zip
`)
expect(headers).toEqual({})
})
})