1 | 'use strict'
|
2 |
|
3 | const fs = require('fs')
|
4 | const path = require('path')
|
5 | const util = require('util')
|
6 | const IConfiguration = require('./iconfiguration')
|
7 | const { check } = require('./mapping')
|
8 | const checkMethod = require('./checkMethod')
|
9 | const { parse } = require('./schema')
|
10 | const {
|
11 | $configurationInterface,
|
12 | $configurationRequests,
|
13 | $handlerMethod,
|
14 | $handlerSchema
|
15 | } = require('./symbols')
|
16 |
|
17 | const readFileAsync = util.promisify(fs.readFile)
|
18 | const statAsync = util.promisify(fs.stat)
|
19 |
|
20 | const defaultHandlers = {
|
21 | custom: require('./handlers/custom'),
|
22 | file: require('./handlers/file'),
|
23 | status: require('./handlers/status'),
|
24 | url: require('./handlers/url'),
|
25 | use: require('./handlers/use')
|
26 | }
|
27 |
|
28 | const defaults = {
|
29 | hostname: undefined,
|
30 | port: 5000,
|
31 | 'max-redirect': 10,
|
32 | mappings: [{
|
33 | match: /^\/proxy\/(https?)\/(.*)/,
|
34 | url: '$1://$2',
|
35 | 'unsecure-cookies': true
|
36 | }, {
|
37 | match: '(.*)',
|
38 | file: './$1'
|
39 | }]
|
40 | }
|
41 |
|
42 | function applyDefaults (configuration) {
|
43 | Object.keys(defaults).forEach(property => {
|
44 | if (!Object.prototype.hasOwnProperty.call(configuration, property)) {
|
45 | configuration[property] = defaults[property]
|
46 | }
|
47 | })
|
48 | }
|
49 |
|
50 | function getHandler (handlers, types, mapping) {
|
51 | for (let index = 0; index < types.length; ++index) {
|
52 | const type = types[index]
|
53 | const redirect = mapping[type]
|
54 | if (redirect !== undefined) {
|
55 | return {
|
56 | handler: handlers[type],
|
57 | redirect,
|
58 | type
|
59 | }
|
60 | }
|
61 | }
|
62 | return {}
|
63 | }
|
64 |
|
65 | function checkHandler (handler, type) {
|
66 | if (handler.schema) {
|
67 | handler[$handlerSchema] = parse(handler.schema)
|
68 | delete handler.schema
|
69 | }
|
70 | if (handler.method) {
|
71 | checkMethod(handler, $handlerMethod)
|
72 | delete handler.method
|
73 | }
|
74 | if (typeof handler.redirect !== 'function') {
|
75 | throw new Error('Invalid "' + type + '" handler: redirect is not a function')
|
76 | }
|
77 | }
|
78 |
|
79 | function validateHandler (type) {
|
80 | const handlers = this.handlers
|
81 | let handler = handlers[type]
|
82 | if (typeof handler === 'string') {
|
83 | handler = require(handler)
|
84 | handlers[type] = handler
|
85 | }
|
86 | checkHandler(handler, type)
|
87 | Object.freeze(handler)
|
88 | }
|
89 |
|
90 | function setHandlers (configuration) {
|
91 | if (configuration.handlers) {
|
92 |
|
93 | configuration.handlers = Object.assign({}, configuration.handlers, defaultHandlers)
|
94 | } else {
|
95 | configuration.handlers = defaultHandlers
|
96 | }
|
97 | Object.keys(configuration.handlers).forEach(validateHandler.bind(configuration))
|
98 | configuration.handler = getHandler.bind(null, configuration.handlers, Object.keys(configuration.handlers))
|
99 | }
|
100 |
|
101 | async function readSslFile (configuration, filePath) {
|
102 | if (path.isAbsolute(filePath)) {
|
103 | return (await readFileAsync(filePath)).toString()
|
104 | }
|
105 | return (await readFileAsync(path.join(configuration.ssl.cwd, filePath))).toString()
|
106 | }
|
107 |
|
108 | async function checkProtocol (configuration) {
|
109 | if (configuration.ssl) {
|
110 | configuration.protocol = 'https'
|
111 | configuration.ssl.cert = await readSslFile(configuration, configuration.ssl.cert)
|
112 | configuration.ssl.key = await readSslFile(configuration, configuration.ssl.key)
|
113 | } else {
|
114 | configuration.protocol = 'http'
|
115 | }
|
116 | }
|
117 |
|
118 | async function checkMappings (configuration) {
|
119 | const configurationInterface = new IConfiguration(configuration)
|
120 | configuration[$configurationInterface] = configurationInterface
|
121 | for await (const mapping of configuration.mappings) {
|
122 | await check(configuration, mapping)
|
123 | }
|
124 | }
|
125 |
|
126 | function setCwd (folderPath, configuration) {
|
127 | if (configuration.handlers) {
|
128 | Object.keys(configuration.handlers).forEach(prefix => {
|
129 | var handler = configuration.handlers[prefix]
|
130 | if (typeof handler === 'string' && handler.match(/^\.\.?\//)) {
|
131 | configuration.handlers[prefix] = path.join(folderPath, handler)
|
132 | }
|
133 | })
|
134 | }
|
135 | if (configuration.mappings) {
|
136 | configuration.mappings.forEach(mapping => {
|
137 | if (!mapping.cwd) {
|
138 | mapping.cwd = folderPath
|
139 | }
|
140 | })
|
141 | }
|
142 | if (configuration.ssl && !configuration.ssl.cwd) {
|
143 | configuration.ssl.cwd = folderPath
|
144 | }
|
145 | }
|
146 |
|
147 | function extend (filePath, configuration) {
|
148 | const folderPath = path.dirname(filePath)
|
149 | setCwd(folderPath, configuration)
|
150 | if (configuration.extend) {
|
151 | const basefilePath = path.join(folderPath, configuration.extend)
|
152 | delete configuration.extend
|
153 | return readFileAsync(basefilePath)
|
154 | .then(buffer => JSON.parse(buffer.toString()))
|
155 | .then(baseConfiguration => {
|
156 |
|
157 | const baseMappings = baseConfiguration.mappings
|
158 | const mergedConfiguration = Object.assign(baseConfiguration, configuration)
|
159 | if (baseMappings !== mergedConfiguration.mappings) {
|
160 | mergedConfiguration.mappings = [...configuration.mappings, ...baseMappings]
|
161 | }
|
162 | return extend(basefilePath, mergedConfiguration)
|
163 | })
|
164 | }
|
165 | return configuration
|
166 | }
|
167 |
|
168 | module.exports = {
|
169 | async check (configuration) {
|
170 | const checkedConfiguration = Object.assign({}, configuration)
|
171 | applyDefaults(checkedConfiguration)
|
172 | setHandlers(checkedConfiguration)
|
173 | await checkProtocol(checkedConfiguration)
|
174 | await checkMappings(checkedConfiguration)
|
175 | checkedConfiguration[$configurationRequests] = {
|
176 | hold: Promise.resolve(),
|
177 | promises: []
|
178 | }
|
179 | return checkedConfiguration
|
180 | },
|
181 |
|
182 | async read (fileName) {
|
183 | let filePath
|
184 | if (path.isAbsolute(fileName)) {
|
185 | filePath = fileName
|
186 | } else {
|
187 | filePath = path.join(process.cwd(), fileName)
|
188 | }
|
189 | return statAsync(filePath)
|
190 | .then(() => readFileAsync(filePath).then(buffer => JSON.parse(buffer.toString())))
|
191 | .then(configuration => extend(filePath, configuration))
|
192 | }
|
193 | }
|