1 // ============LICENSE_START===============================================
2 // Copyright (C) 2021 Nordix Foundation. All rights reserved.
3 // ========================================================================
4 // Licensed under the Apache License, Version 2.0 (the "License");
5 // you may not use this file except in compliance with the License.
6 // You may obtain a copy of the License at
8 // http://www.apache.org/licenses/LICENSE-2.0
10 // Unless required by applicable law or agreed to in writing, software
11 // distributed under the License is distributed on an "AS IS" BASIS,
12 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 // See the License for the specific language governing permissions and
14 // limitations under the License.
15 // ============LICENSE_END=================================================
17 // Basic http/https proxy
18 // Call the the proxy on 8080/8433 for http/https
19 // The destination (proxied) protocol may be http or https
20 // Proxy healthcheck on 8081/8434 for http/https - answers with statistics in json
22 const http = require('http');
23 const net = require('net');
24 const urlp = require('url');
25 const process = require('process')
26 const https = require('https');
27 const fs = require('fs');
29 // Proxy server port for http
30 const proxyport = 8080;
31 // Proxy server port for https
32 const proxyporthttps = 8433;
33 // Proxy server alive check, port for http
34 const aliveport = 8081;
35 // Proxy server alive check, port for https
36 const aliveporthttps = 8434;
38 // Default https destination port
39 const defaulthttpsport = "443";
43 // Certs etc for https
44 const httpsoptions = {
45 key: fs.readFileSync('cert/key.crt'),
46 cert: fs.readFileSync('cert/cert.crt'),
47 passphrase: fs.readFileSync('cert/pass', 'utf8')
51 'http-requests-initiated': 0,
52 'http-requests-failed': 0,
53 'https-requests-initiated': 0,
54 'https-requests-failed': 0
57 // handle a http proxy request
58 function httpclientrequest(clientrequest, clientresponse) {
59 stats['http-requests-initiated']++;
61 // Extract destination information
62 var crurl=clientrequest.url;
63 var crhost=clientrequest.headers['host'];
64 var crproto=clientrequest.headers['x-forwarded-proto'];
67 console.log("crurl: "+crurl)
68 console.log("crhost: "+crhost)
69 console.log("crproto: "+crproto)
72 // If this server is running behind a proxy (like istio envoy proxy) then the 'clientrequest.url'
73 // only contains the path component (i.e /test ). The host name and port is included in the
74 // 'host' header and the protocol (http/https) is in the header 'x-forwarded-proto'.
75 // In case of istio - https to a pod over mTLS does not seem to work. Only http.
76 // Othewise, if no front proxy, the full url is included in the 'clientrequest.url'
77 if (crproto != undefined) {
78 crurl=crproto+"://"+crhost+crurl
80 console.log(" Constructed url: "+crurl)
82 } else if (crurl.startsWith('/')) {
83 console.log("Catched bad url in http request: "+crurl)
88 const clientrequesturl = new URL(crurl);
90 var proxyrequestoptions = {
91 'host': clientrequesturl.hostname,
92 'port': clientrequesturl.port,
93 'method': clientrequest.method,
94 'path': clientrequesturl.pathname+clientrequesturl.search,
95 'agent': clientrequest.agent,
96 'auth': clientrequest.auth,
97 'headers': clientrequest.headers
100 // Setup connection to destination
101 var proxyrequest = http.request(
103 function (proxyresponse) {
104 clientresponse.writeHead(proxyresponse.statusCode, proxyresponse.headers);
105 proxyresponse.on('data', function (chunk) {
106 clientresponse.write(chunk);
108 proxyresponse.on('end', function () {
109 clientresponse.end();
115 // Handle the connection and data transfer between source and destination
116 proxyrequest.on('error', function (error) {
117 clientresponse.writeHead(500);
118 stats['http-requests-failed']++;
120 clientresponse.write("<h1>500 Error</h1>\r\n" + "<p>Error was <pre>" + error + "</pre></p>\r\n" + "</body></html>\r\n");
121 clientresponse.end();
123 clientrequest.addListener('data', function (chunk) {
124 proxyrequest.write(chunk);
126 clientrequest.addListener('end', function () {
131 // Function to add a 'connect' message listener to a http server
132 function addhttpsconnect(httpserver) {
133 httpserver.addListener(
135 function (request, socketrequest, bodyhead) {
138 console.log("Received 'connect' for: "+request['url'])
140 stats['https-requests-initiated']++;
141 // Extract destination information
142 var res = request['url'].split(":")
143 var hostname = res[0]
144 var port = defaulthttpsport;
145 if (res[1] != null) {
149 // Setup connection to destination
150 var httpversion = request['httpVersion'];
151 var proxysocket = new net.Socket();
154 parseInt(port), hostname,
156 proxysocket.write(bodyhead);
157 socketrequest.write("HTTP/" + httpversion + " 200 Connection established\r\n\r\n");
161 // Handle the connection and data transfer between source and destination
162 proxysocket.on('data', function (chunk) {
163 socketrequest.write(chunk);
165 proxysocket.on('end', function () {
169 socketrequest.on('data', function (chunk) {
170 proxysocket.write(chunk);
172 socketrequest.on('end', function () {
176 proxysocket.on('error', function (err) {
177 stats['https-requests-failed']++;
179 socketrequest.write("HTTP/" + httpversion + " 500 Connection error\r\n\r\n");
182 socketrequest.on('error', function (err) {
183 stats['https-requests-failed']++;
193 // -------------------- Alive server ----------------------------------
194 // Respond with '200' and statistics for any path (except for GET|PUT|DELETE on /debug) on the alive address
195 const alivelistener = function (req, res) {
196 if (req.url == "/debug") {
197 if (req.method == "GET") {
198 res.writeHead(200, { 'Content-Type': 'text/plain' });
202 } else if (req.method == "PUT") {
204 res.writeHead(200, { 'Content-Type': 'text/plain' });
208 } else if (req.method == "DELETE") {
210 res.writeHead(200, { 'Content-Type': 'text/plain' });
217 res.writeHead(200, { 'Content-Type': 'application/json' });
218 res.write(JSON.stringify(stats))
222 // The alive server - for healthckeck
223 const aliveserver = http.createServer(alivelistener);
225 // The alive server - for healthckeck
226 const aliveserverhttps = https.createServer(httpsoptions, alivelistener);
228 //Handle heatlhcheck requests
229 aliveserver.listen(aliveport, () => {
230 console.log('alive server on: '+aliveport);
231 console.log(' example: curl localhost:'+aliveport)
234 //Handle heatlhcheck requests
235 aliveserverhttps.listen(aliveporthttps, () => {
236 console.log('alive server on: '+aliveporthttps);
237 console.log(' example: curl -k https://localhost:'+aliveporthttps)
240 // -------------------- Proxy server ---------------------------------
243 const proxyserver = http.createServer(httpclientrequest).listen(proxyport);
244 console.log('http/https proxy for http proxy calls on port ' + proxyport);
245 console.log(' example: curl --proxy http://localhost:8080 http://100.110.120.130:1234')
246 console.log(' example: curl -k --proxy http//localhost:8080 https://100.110.120.130:5678')
248 // handle a http proxy request - https listener
249 addhttpsconnect(proxyserver);
251 const proxyserverhttps = https.createServer(httpsoptions, httpclientrequest).listen(proxyporthttps);
252 console.log('http/https proxy for https proxy calls on port ' + proxyporthttps);
253 console.log(' example: curl --proxy-insecure --proxy https://localhost:8433 http://100.110.120.130:1234')
254 console.log(' example: curl -k --proxy-insecure --proxy https://localhost:8433 https://100.110.120.130:5678')
256 // handle a https proxy request - https listener
257 addhttpsconnect(proxyserverhttps);
261 //Handle ctrl c when running in interactive mode
262 process.on('SIGINT', () => {
263 console.info("Interrupted")