mirror of
https://github.com/sasjs/server.git
synced 2026-01-14 17:30:05 +00:00
Merge pull request #71 from sasjs/return-file-in-response
Return file in response
This commit is contained in:
@@ -539,7 +539,9 @@ paths:
|
|||||||
content:
|
content:
|
||||||
application/json:
|
application/json:
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/components/schemas/ExecuteReturnJsonResponse'
|
anyOf:
|
||||||
|
- {$ref: '#/components/schemas/ExecuteReturnJsonResponse'}
|
||||||
|
- {type: string, format: byte}
|
||||||
description: 'Execute SAS code.'
|
description: 'Execute SAS code.'
|
||||||
summary: 'Run SAS Code and returns log'
|
summary: 'Run SAS Code and returns log'
|
||||||
tags:
|
tags:
|
||||||
@@ -1054,7 +1056,9 @@ paths:
|
|||||||
content:
|
content:
|
||||||
application/json:
|
application/json:
|
||||||
schema:
|
schema:
|
||||||
type: string
|
anyOf:
|
||||||
|
- {type: string}
|
||||||
|
- {type: string, format: byte}
|
||||||
description: "Trigger a SAS program using it's location in the _program parameter.\nEnable debugging using the _debug parameter.\nAdditional URL parameters are turned into SAS macro variables.\nAny files provided are placed into the session and\ncorresponding _WEBIN_XXX variables are created."
|
description: "Trigger a SAS program using it's location in the _program parameter.\nEnable debugging using the _debug parameter.\nAdditional URL parameters are turned into SAS macro variables.\nAny files provided are placed into the session and\ncorresponding _WEBIN_XXX variables are created."
|
||||||
summary: 'Execute Stored Program, return raw content'
|
summary: 'Execute Stored Program, return raw content'
|
||||||
tags:
|
tags:
|
||||||
@@ -1078,7 +1082,9 @@ paths:
|
|||||||
content:
|
content:
|
||||||
application/json:
|
application/json:
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/components/schemas/ExecuteReturnJsonResponse'
|
anyOf:
|
||||||
|
- {$ref: '#/components/schemas/ExecuteReturnJsonResponse'}
|
||||||
|
- {type: string, format: byte}
|
||||||
examples:
|
examples:
|
||||||
'Example 1':
|
'Example 1':
|
||||||
value: {status: success, _webout: 'webout content', log: [], httpHeaders: {Content-type: application/zip, Cache-Control: 'public, max-age=1000'}}
|
value: {status: success, _webout: 'webout content', log: [], httpHeaders: {Content-type: application/zip, Cache-Control: 'public, max-age=1000'}}
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ export class CodeController {
|
|||||||
public async executeSASCode(
|
public async executeSASCode(
|
||||||
@Request() request: express.Request,
|
@Request() request: express.Request,
|
||||||
@Body() body: ExecuteSASCodePayload
|
@Body() body: ExecuteSASCodePayload
|
||||||
): Promise<ExecuteReturnJsonResponse> {
|
): Promise<ExecuteReturnJsonResponse | Buffer> {
|
||||||
return executeSASCode(request, body)
|
return executeSASCode(request, body)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -41,6 +41,11 @@ const executeSASCode = async (req: any, { code }: ExecuteSASCodePayload) => {
|
|||||||
true
|
true
|
||||||
)) as ExecuteReturnJson
|
)) as ExecuteReturnJson
|
||||||
|
|
||||||
|
if (webout instanceof Buffer) {
|
||||||
|
;(req as any).sasHeaders = httpHeaders
|
||||||
|
return webout
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
status: 'success',
|
status: 'success',
|
||||||
_webout: webout,
|
_webout: webout,
|
||||||
|
|||||||
@@ -1,7 +1,13 @@
|
|||||||
import path from 'path'
|
import path from 'path'
|
||||||
import fs from 'fs'
|
import fs from 'fs'
|
||||||
import { getSessionController } from './'
|
import { getSessionController } from './'
|
||||||
import { readFile, fileExists, createFile, moveFile } from '@sasjs/utils'
|
import {
|
||||||
|
readFile,
|
||||||
|
fileExists,
|
||||||
|
createFile,
|
||||||
|
moveFile,
|
||||||
|
readFileBinary
|
||||||
|
} from '@sasjs/utils'
|
||||||
import { PreProgramVars, TreeNode } from '../../types'
|
import { PreProgramVars, TreeNode } from '../../types'
|
||||||
import {
|
import {
|
||||||
extractHeaders,
|
extractHeaders,
|
||||||
@@ -16,12 +22,12 @@ export interface ExecutionVars {
|
|||||||
|
|
||||||
export interface ExecuteReturnRaw {
|
export interface ExecuteReturnRaw {
|
||||||
httpHeaders: HTTPHeaders
|
httpHeaders: HTTPHeaders
|
||||||
result: string
|
result: string | Buffer
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ExecuteReturnJson {
|
export interface ExecuteReturnJson {
|
||||||
httpHeaders: HTTPHeaders
|
httpHeaders: HTTPHeaders
|
||||||
webout: string
|
webout: string | Buffer
|
||||||
log?: string
|
log?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -138,15 +144,17 @@ ${program}`
|
|||||||
}
|
}
|
||||||
|
|
||||||
const log = (await fileExists(logPath)) ? await readFile(logPath) : ''
|
const log = (await fileExists(logPath)) ? await readFile(logPath) : ''
|
||||||
const webout = (await fileExists(weboutPath))
|
|
||||||
? await readFile(weboutPath)
|
|
||||||
: ''
|
|
||||||
const headersContent = (await fileExists(headersPath))
|
const headersContent = (await fileExists(headersPath))
|
||||||
? await readFile(headersPath)
|
? await readFile(headersPath)
|
||||||
: ''
|
: ''
|
||||||
const httpHeaders: HTTPHeaders = headersContent
|
const httpHeaders: HTTPHeaders = extractHeaders(headersContent)
|
||||||
? extractHeaders(headersContent)
|
const fileResponse: boolean = httpHeaders.hasOwnProperty('content-type')
|
||||||
: {}
|
|
||||||
|
const webout = (await fileExists(weboutPath))
|
||||||
|
? fileResponse
|
||||||
|
? await readFileBinary(weboutPath)
|
||||||
|
: await readFile(weboutPath)
|
||||||
|
: ''
|
||||||
|
|
||||||
const debugValue =
|
const debugValue =
|
||||||
typeof vars._debug === 'string' ? parseInt(vars._debug) : vars._debug
|
typeof vars._debug === 'string' ? parseInt(vars._debug) : vars._debug
|
||||||
@@ -165,10 +173,11 @@ ${program}`
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
httpHeaders,
|
httpHeaders,
|
||||||
result:
|
result: fileResponse
|
||||||
(debugValue && debugValue >= 131) || session.crashed
|
? webout
|
||||||
? `<html><body>${webout}<div style="text-align:left"><hr /><h2>SAS Log</h2><pre>${log}</pre></div></body></html>`
|
: (debugValue && debugValue >= 131) || session.crashed
|
||||||
: webout
|
? `<html><body>${webout}<div style="text-align:left"><hr /><h2>SAS Log</h2><pre>${log}</pre></div></body></html>`
|
||||||
|
: webout
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -59,7 +59,7 @@ export class STPController {
|
|||||||
public async executeReturnRaw(
|
public async executeReturnRaw(
|
||||||
@Request() request: express.Request,
|
@Request() request: express.Request,
|
||||||
@Query() _program: string
|
@Query() _program: string
|
||||||
): Promise<string> {
|
): Promise<string | Buffer> {
|
||||||
return executeReturnRaw(request, _program)
|
return executeReturnRaw(request, _program)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -87,7 +87,7 @@ export class STPController {
|
|||||||
@Request() request: express.Request,
|
@Request() request: express.Request,
|
||||||
@Body() body?: ExecuteReturnJsonPayload,
|
@Body() body?: ExecuteReturnJsonPayload,
|
||||||
@Query() _program?: string
|
@Query() _program?: string
|
||||||
): Promise<ExecuteReturnJsonResponse> {
|
): Promise<ExecuteReturnJsonResponse | Buffer> {
|
||||||
const program = _program ?? body?._program
|
const program = _program ?? body?._program
|
||||||
return executeReturnJson(request, program!)
|
return executeReturnJson(request, program!)
|
||||||
}
|
}
|
||||||
@@ -96,7 +96,7 @@ export class STPController {
|
|||||||
const executeReturnRaw = async (
|
const executeReturnRaw = async (
|
||||||
req: express.Request,
|
req: express.Request,
|
||||||
_program: string
|
_program: string
|
||||||
): Promise<string> => {
|
): Promise<string | Buffer> => {
|
||||||
const query = req.query as ExecutionVars
|
const query = req.query as ExecutionVars
|
||||||
const sasCodePath =
|
const sasCodePath =
|
||||||
path
|
path
|
||||||
@@ -113,7 +113,11 @@ const executeReturnRaw = async (
|
|||||||
|
|
||||||
req.res?.set(httpHeaders)
|
req.res?.set(httpHeaders)
|
||||||
|
|
||||||
return result as string
|
if (result instanceof Buffer) {
|
||||||
|
;(req as any).sasHeaders = httpHeaders
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
throw {
|
throw {
|
||||||
code: 400,
|
code: 400,
|
||||||
@@ -127,7 +131,7 @@ const executeReturnRaw = async (
|
|||||||
const executeReturnJson = async (
|
const executeReturnJson = async (
|
||||||
req: any,
|
req: any,
|
||||||
_program: string
|
_program: string
|
||||||
): Promise<ExecuteReturnJsonResponse> => {
|
): Promise<ExecuteReturnJsonResponse | Buffer> => {
|
||||||
const sasCodePath =
|
const sasCodePath =
|
||||||
path
|
path
|
||||||
.join(getTmpFilesFolderPath(), _program)
|
.join(getTmpFilesFolderPath(), _program)
|
||||||
@@ -145,6 +149,11 @@ const executeReturnJson = async (
|
|||||||
true
|
true
|
||||||
)) as ExecuteReturnJson
|
)) as ExecuteReturnJson
|
||||||
|
|
||||||
|
if (webout instanceof Buffer) {
|
||||||
|
;(req as any).sasHeaders = httpHeaders
|
||||||
|
return webout
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
status: 'success',
|
status: 'success',
|
||||||
_webout: webout,
|
_webout: webout,
|
||||||
|
|||||||
@@ -12,6 +12,12 @@ runRouter.post('/execute', async (req, res) => {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await controller.executeSASCode(req, body)
|
const response = await controller.executeSASCode(req, body)
|
||||||
|
|
||||||
|
if (response instanceof Buffer) {
|
||||||
|
res.writeHead(200, (req as any).sasHeaders)
|
||||||
|
return res.end(response)
|
||||||
|
}
|
||||||
|
|
||||||
res.send(response)
|
res.send(response)
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
const statusCode = err.code
|
const statusCode = err.code
|
||||||
|
|||||||
@@ -14,6 +14,12 @@ stpRouter.get('/execute', async (req, res) => {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await controller.executeReturnRaw(req, query._program)
|
const response = await controller.executeReturnRaw(req, query._program)
|
||||||
|
|
||||||
|
if (response instanceof Buffer) {
|
||||||
|
res.writeHead(200, (req as any).sasHeaders)
|
||||||
|
return res.end(response)
|
||||||
|
}
|
||||||
|
|
||||||
res.send(response)
|
res.send(response)
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
const statusCode = err.code
|
const statusCode = err.code
|
||||||
@@ -40,6 +46,12 @@ stpRouter.post(
|
|||||||
body,
|
body,
|
||||||
query?._program
|
query?._program
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if (response instanceof Buffer) {
|
||||||
|
res.writeHead(200, (req as any).sasHeaders)
|
||||||
|
return res.end(response)
|
||||||
|
}
|
||||||
|
|
||||||
res.send(response)
|
res.send(response)
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
const statusCode = err.code
|
const statusCode = err.code
|
||||||
|
|||||||
@@ -4,20 +4,20 @@ export interface HTTPHeaders {
|
|||||||
[key: string]: string
|
[key: string]: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export const extractHeaders = (content: string): HTTPHeaders => {
|
export const extractHeaders = (content?: string): HTTPHeaders => {
|
||||||
const headersObj: HTTPHeaders = {}
|
const headersObj: HTTPHeaders = {}
|
||||||
const headersArr = content
|
const headersArr = content
|
||||||
.split('\n')
|
?.split('\n')
|
||||||
.map((line) => line.trim())
|
.map((line) => line.trim())
|
||||||
.filter((line) => !!line)
|
.filter((line) => !!line)
|
||||||
|
|
||||||
headersArr.forEach((headerStr) => {
|
headersArr?.forEach((headerStr) => {
|
||||||
const [key, value] = headerStr.split(':').map((data) => data.trim())
|
const [key, value] = headerStr.split(':').map((data) => data.trim())
|
||||||
|
|
||||||
if (value && headerUtils.validateHeader(key, value)) {
|
if (value && headerUtils.validateHeader(key, value)) {
|
||||||
headersObj[key] = value
|
headersObj[key.toLowerCase()] = value
|
||||||
} else {
|
} else {
|
||||||
delete headersObj[key]
|
delete headersObj[key.toLowerCase()]
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -12,8 +12,8 @@ describe('extractHeaders', () => {
|
|||||||
`)
|
`)
|
||||||
|
|
||||||
expect(headers).toEqual({
|
expect(headers).toEqual({
|
||||||
'Content-type': 'application/zip',
|
'content-type': 'application/zip',
|
||||||
'Cache-Control': 'public, max-age=1000'
|
'cache-control': 'public, max-age=1000'
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -25,7 +25,7 @@ describe('extractHeaders', () => {
|
|||||||
Content-type:
|
Content-type:
|
||||||
`)
|
`)
|
||||||
|
|
||||||
expect(headers).toEqual({ 'Cache-Control': 'public, max-age=1000' })
|
expect(headers).toEqual({ 'cache-control': 'public, max-age=1000' })
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should return only valid http headers', () => {
|
it('should return only valid http headers', () => {
|
||||||
@@ -37,4 +37,16 @@ describe('extractHeaders', () => {
|
|||||||
|
|
||||||
expect(headers).toEqual({})
|
expect(headers).toEqual({})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('should return http headers if empty', () => {
|
||||||
|
const headers = extractHeaders('')
|
||||||
|
|
||||||
|
expect(headers).toEqual({})
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should return http headers if not provided', () => {
|
||||||
|
const headers = extractHeaders()
|
||||||
|
|
||||||
|
expect(headers).toEqual({})
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
Reference in New Issue
Block a user