0bab0523b55b146b2c2a8870a62412255af0fa0b
[nonrtric.git] / test / http-https-proxy / http_proxy.js
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
7 //
8 //       http://www.apache.org/licenses/LICENSE-2.0
9 //
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=================================================
16
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
21
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');
28
29 // Proxy server port for http
30 const proxyport = 8080;
31 // Proxy server port for https
32 const proxyporthttps = 8433;
33 // Proyx server alive check, port for http
34 const aliveport = 8081;
35 // Proyx server alive check,  port for https
36 const aliveporthttps = 8434;
37
38 // Default https destination port
39 const defaulthttpsport = "443";
40
41 var debug = false;
42
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')
48 };
49
50 const stats = {
51   'http-requests-initiated': 0,
52   'http-requests-failed': 0,
53   'https-requests-initiated': 0,
54   'https-requests-failed': 0
55 };
56
57 // handle a http proxy request
58 function httpclientrequest(clientrequest, clientresponse) {
59   stats['http-requests-initiated']++;
60
61   // Extract destination information
62   var crurl=clientrequest.url;
63   var crhost=clientrequest.headers['host'];
64   var crproto=clientrequest.headers['x-forwarded-proto'];
65
66   if (debug) {
67     console.log("crurl: "+crurl)
68     console.log("crhost: "+crhost)
69     console.log("crproto: "+crproto)
70   }
71
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
79     if (debug) {
80       console.log(" Constructed ulr: "+crurl)
81     }
82   } else if (crurl.startsWith('/')) {
83     console.log("Catched bad url in http request: "+crurl)
84     clientresponse.end();
85     return;
86   }
87
88   const clientrequesturl = new URL(crurl);
89
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
98   };
99
100   // Setup connection to destination
101   var proxyrequest = http.request(
102     proxyrequestoptions,
103     function (proxyresponse) {
104       clientresponse.writeHead(proxyresponse.statusCode, proxyresponse.headers);
105       proxyresponse.on('data', function (chunk) {
106         clientresponse.write(chunk);
107       });
108       proxyresponse.on('end', function () {
109         clientresponse.end();
110       });
111
112     }
113   );
114
115   // Handle the connection and data transfer between source and desitnation
116   proxyrequest.on('error', function (error) {
117     clientresponse.writeHead(500);
118     stats['http-requests-failed']++;
119     console.log(error);
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();
122   });
123   clientrequest.addListener('data', function (chunk) {
124     proxyrequest.write(chunk);
125   });
126   clientrequest.addListener('end', function () {
127     proxyrequest.end();
128   });
129 }
130
131 // Function to add a 'connect' message listener to a http server
132 function addhttpsconnect(httpserver) {
133   httpserver.addListener(
134     'connect',
135     function (request, socketrequest, bodyhead) {
136
137       if (debug) {
138         console.log("Received 'connect' for: "+request['url'])
139       }
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) {
146         port = res[1]
147       }
148
149       // Setup connection to destination
150       var httpversion = request['httpVersion'];
151       var proxysocket = new net.Socket();
152
153       proxysocket.connect(
154         parseInt(port), hostname,
155         function () {
156           proxysocket.write(bodyhead);
157           socketrequest.write("HTTP/" + httpversion + " 200 Connection established\r\n\r\n");
158         }
159       );
160
161       // Handle the connection and data transfer between source and desitnation
162       proxysocket.on('data', function (chunk) {
163         socketrequest.write(chunk);
164       });
165       proxysocket.on('end', function () {
166         socketrequest.end();
167       });
168
169       socketrequest.on('data', function (chunk) {
170         proxysocket.write(chunk);
171       });
172       socketrequest.on('end', function () {
173         proxysocket.end();
174       });
175
176       proxysocket.on('error', function (err) {
177         stats['https-requests-failed']++;
178         console.log(err);
179         socketrequest.write("HTTP/" + httpversion + " 500 Connection error\r\n\r\n");
180         socketrequest.end();
181       });
182       socketrequest.on('error', function (err) {
183         stats['https-requests-failed']++;
184         console.log(err);
185         proxysocket.end();
186       });
187     }
188   );
189 }
190
191 function main() {
192
193   // -------------------- Alive server ----------------------------------
194   // Responde 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' });
199         res.write(""+debug)
200         res.end()
201         return
202       } else if (req.method == "PUT") {
203         debug=true
204         res.writeHead(200, { 'Content-Type': 'text/plain' });
205         res.write("OK")
206         res.end()
207         return
208       } else if (req.method == "DELETE") {
209         debug=false
210         res.writeHead(200, { 'Content-Type': 'text/plain' });
211         res.write("OK")
212         res.end()
213         return
214       }
215     }
216     console.log(stats)
217     res.writeHead(200, { 'Content-Type': 'application/json' });
218     res.write(JSON.stringify(stats))
219     res.end();
220   };
221
222   // The alive server - for healthckeck
223   const aliveserver = http.createServer(alivelistener);
224
225   // The alive server - for healthckeck
226   const aliveserverhttps = https.createServer(httpsoptions, alivelistener);
227
228   //Handle heatlhcheck requests
229   aliveserver.listen(aliveport, () => {
230     console.log('alive server on: '+aliveport);
231     console.log(' example: curl localhost:'+aliveport)
232   });
233
234   //Handle heatlhcheck requests
235   aliveserverhttps.listen(aliveporthttps, () => {
236     console.log('alive server on: '+aliveporthttps);
237     console.log(' example: curl -k https://localhost:'+aliveporthttps)
238   });
239
240   // -------------------- Proxy server ---------------------------------
241
242   // The 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')
247
248   // handle a http proxy request - https listener
249   addhttpsconnect(proxyserver);
250
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')
255
256   // handle a https proxy request - https listener
257   addhttpsconnect(proxyserverhttps);
258
259 }
260
261 //Handle ctrl c when running in interactive mode
262 process.on('SIGINT', () => {
263   console.info("Interrupted")
264   process.exit(0)
265 })
266
267 main();