{"id":26092897,"url":"https://github.com/jazzshu/reverse-proxy","last_synced_at":"2026-04-16T20:45:07.125Z","repository":{"id":143113501,"uuid":"393413855","full_name":"jazzshu/reverse-proxy","owner":"jazzshu","description":"Reverse proxy project created as a test assignment for a job interview ","archived":false,"fork":false,"pushed_at":"2026-03-03T12:54:27.000Z","size":1107,"stargazers_count":2,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"master","last_synced_at":"2026-03-03T16:34:24.578Z","etag":null,"topics":["http","https","interview","javascript","job","nodejs","proxy","reverse-proxy"],"latest_commit_sha":null,"homepage":"","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/jazzshu.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2021-08-06T15:10:01.000Z","updated_at":"2026-03-03T12:54:33.000Z","dependencies_parsed_at":"2024-01-23T10:14:43.120Z","dependency_job_id":null,"html_url":"https://github.com/jazzshu/reverse-proxy","commit_stats":null,"previous_names":["jazzshu/reverse-proxy"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/jazzshu/reverse-proxy","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jazzshu%2Freverse-proxy","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jazzshu%2Freverse-proxy/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jazzshu%2Freverse-proxy/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jazzshu%2Freverse-proxy/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/jazzshu","download_url":"https://codeload.github.com/jazzshu/reverse-proxy/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jazzshu%2Freverse-proxy/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":31904067,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-16T18:22:33.417Z","status":"ssl_error","status_checked_at":"2026-04-16T18:21:47.142Z","response_time":69,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.6:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["http","https","interview","javascript","job","nodejs","proxy","reverse-proxy"],"created_at":"2025-03-09T11:10:22.634Z","updated_at":"2026-04-16T20:45:07.086Z","avatar_url":"https://github.com/jazzshu.png","language":"JavaScript","readme":"# Reverse Proxy\n## Usage\nIn order to run the program:\n1. Create a new folder and clone the repository using the following commands on a git shell:\n- git init\n- git clone https://github.com/JasonShuyinta/reverse-proxy.git\n2. Once the repository is downloaded, navigate to the main folder with the following command:\n- cd reverse-proxy\n3. Install all the necessary dependencies with the npm command:\n- npm install\n4. Once the installation is finished, open 4 terminals, navigate with each of them to the reverse-proxy folder and run the following commands:\n- node run-server-one.js\n- node run-server-two.js\n- node run-reverse-proxy.js\n- npm start\n\n\nIf the steps are done correctly, a web page should open at http://localhost:3000\n\n### Demo\nIn the web page you will see some radio buttons. Choose a load balancing strategy or directly a server. After you made your choice\ninsert some numbers in the fields and hit the \"SUM\" button. You will see the result and which server made the calculation, plus \nyou will be able to see the usage percentage of each server. \nIf for example you choose \"Server One\" and hit the SUM button multiple times, you will see the usage percentage of that server increase, and \nthe other one decrease. \nNow choose the \"Round Robin\" strategy and hit the SUM button again multiple times: you will see the usage percentages convey towards 50%.\n\nTo demo the cache mechanism, choose any radio button. Hit the \"GET DATA\" button: a table will appear with some random data. It will be possible \nto see the time elapsed to obtain the response (usually \u003e 150 milliseconds) and the server that was called. If you hit the button again within 5 seconds\nfrom the previous click you will see  that the time elapsed will be much lower (usually \u003c 70 milliseconds) and no more the server name but the \"Cache hit!\" expression.\nWaiting 5 seconds will cancel the cache and one of the servers will be called again.\n\n### Test\nTo run the tests open a new terminal, navigate to the reverse-proxy folder and run the following command:\n- npm test\n\n\n## Documentation\nThis example of a reverse proxy has been done using [NodeJs](https://nodejs.org/en/) and [ExpressJs](https://expressjs.com). \nThe reason for using this framework is that it is simple to configure and it has a lot of documentation over the web with an active community.\n\n### Servers \nFirst of all, the 2 servers were created: they are identical, running on different ports (the addresses\nare the same, but changing them will not affect the functionality of the program). There are just\ntwo simple operations done by the servers: “sumNumbers” and “getData”.\n\n- /sumNumbers: it is a POST operation, that takes as input two numbers and returns the\nsum of these values. Plus, it returns the server address that computed this calculation, in\norder for the client to check which server was involved.\n\n```javascript\napp.post(\"/sumNumbers\", (req, res) =\u003e {\n  counter++;\n  return res.status(200).json({\n    result: parseInt(req.body.numOne) + parseInt(req.body.numTwo),\n    serverName: req.headers.host,\n    counter,\n  });\n});\n```\n- /getData: it is a POST operation as well, and what it does is simply retrieve some random\ndata taken from a mock [API](https://jsonplaceholder.typicode.com/). This operation was\nimplemented to test the caching in the proxy.\n\n```javascript\napp.post(\"/getData\", (req, res) =\u003e {\n  //Mock API to get random data\n  axios\n    .get(\"https://jsonplaceholder.typicode.com/todos\")\n    .then((response) =\u003e {\n      counter++;\n      return res.status(200).json({\n        data: response.data,\n        serverName: req.headers.host,\n        counter,\n      });\n    })\n    .catch((err) =\u003e console.log(err));\n});\n```\n\n### Reverse Proxy\nAfter these two servers were done, I have implemented the reverse proxy. \nEvery request done by the client has to pass first through the proxy before reaching on the target servers. In this proxy\nmany operations could be done, both on the requests and the responses. \nFor example: when the “sumNumbers” request is called, the proxy verifies which load balancing strategy to use in order\nto choose which server will handle the operation. Two strategies are implemented: Random and\nRound Robin. \n\n\nThe first one just randomly picks one of the servers from the array and passes on the\nrequest, the latter picks sequentially a server from the array for subsequent requests that arrive to\nthe proxy: in case one of the servers has served more requests than the other one, a while loop is implemented in order to keep track of\neach servers usage, in this way requests are forwarded to the server that has provided less responses. This was implemented to\nmaintain a balance between the two servers so that they each respond to roughly 50% of the requests when the Round Robin is active.\nIt resembles the mechanism of the Least Connection algorithm, where requests are passed to the server that has the least active connections.\n\n```javascript\n//This load balancer strategy chooses one of the servers randomly to serve the request\nfunction randomLoadBalancer() {\n  var chosenTarget = targets[Math.floor(Math.random() * targets.length)];\n  return chosenTarget;\n}\n\n//The Round Robin strategy selectes sequentially the servers, so that each subsequent request is\n//served by the next server\nfunction roundRobinBalancer() {\n  //the while loop assures that the servers work equally, distributing the incoming requests\n  //to the server that has worked less. It is based on the idea of the Least Connection algorithm. \n  while(Math.abs(serverOneUsageCounter - serverTwoUsageCounter) \u003e 1) {\n    if(serverOneUsageCounter \u003e serverTwoUsageCounter) chosenTarget = targets[1];\n    else chosenTarget = targets[0]\n    return chosenTarget;\n  }\n  var chosenTarget = targets[counter];\n  counter++;\n  if (counter === targets.length) counter = 0;\n  return chosenTarget;\n}\n```\n\nIt was also implemented the possibility to directly choose the server instead of passing through a load balancing strategy just to verify\nthat the Round Robin effectively conveys to the 50% usage on both of the servers.\n\n```javascript\n//Based on the clients decision either select the load balancing strategy, \n//or directly set the server to respond to the request\nproxyApp.use(function (req, res) {\n  \n  switch (req.body.loadBalancer) {\n    case \"random\":\n      chosenTarget = randomLoadBalancer(); break;\n    case \"roundrobin\":\n      chosenTarget = roundRobinBalancer(); break;\n    case \"one\":\n      chosenTarget = targets[0]; break;\n    case \"two\":\n      chosenTarget = targets[1]; break;\n  }\n\n  proxy.web(req, res, { target: chosenTarget });\n});\n```\n\nIn all cases, headers are set, so that the server is able to read JSON data coming from the POST requests and the Host header property is \nalso set with the chosen target given by the load balancing strategy.\n\n```javascript\n  if (req.body) {\n    //In order for the request to pass through the proxy to the server, set the headers of the request\n    // so that it can read JSON data and set the Host to the chosen server address\n    let bodyData = JSON.stringify(req.body);\n    proxyReq.setHeader(\"Content-Type\", \"application/json\");\n    proxyReq.setHeader(\"Content-Length\", Buffer.byteLength(bodyData));\n    proxyReq.setHeader(\"Host\", chosenTarget)\n    proxyReq.write(bodyData);\n  }\n```\n\nThe proxy even gets the responses from the server before passing it back to the client.\nIn particular for the “getData” operation the proxy can store the response in the cache. The cache remains\nstored for only 5 seconds, so that if the same request is done within this timeframe, there is no\nneed to query the server, it simply takes the stored response from the cache and returns it back to\nthe client, saving bandwidth usage and reducing latency.\nCaching could also be done on the client side, saving them directly into the browser, avoiding the\nneed to reach the proxy, reducing even more the response time.\n\n```javascript\n//Set cache storing time to 5 seconds\nconst myCache = new NodeCache({ stdTTL: 5 });\n\n//[omitted code here...]\n\n//If there is request made to the \"/getData\" endpoint the response is stored\n//in the cache. If a request is made to the same endpoint within 5 seconds from the\n//previous request, the response is taken from the cache instead of querying the server,\n//reducing bandwidth usage and latency.\nif (req.path == \"/getData\") {\n  if (myCache.has(\"todos\")) \n    return res.status(200).send(myCache.get(\"todos\"));\n}\n```\n\nBefore passing on the response to the client a simple operation is done to calculate the usage percentage of each server: on each\nAPI call a counter was incremented and returned in the response. In the proxy this counter is used to get the server usage percentage out of all the calls made\nto every server. The total number of calls is mantained only in the proxy, so each server only knows its own usage, ignoring the usage of its peers.\nThe usage property is then added to the response body through a library called \"node-http-proxy-json\". It is important to clarify that the library \nat the moment only supports the gzip, deflate and uncompressed formats, and so assure that the servers use one of these compression formats. \n\n```javascript\nmodifyResponse(res, proxyRes, function (body) {\n  if (body) {\n      var totalUsage = serverOneUsageCounter + serverTwoUsageCounter;\n      body.serverOneUsage = Math.floor((serverOneUsageCounter / totalUsage) * 100)\n      body.serverTwoUsage = Math.floor((serverTwoUsageCounter / totalUsage) * 100)\n      delete body.version;\n  }\n  return body;\n})\n```\n\nOn the app first run, there is an API call made by the client to the \"/parseYaml\" endpoint. What it does is simply parse the \n\"config.yaml\" file into a JSON object (through the \"js-yaml\" library) and it uses this data to link the proxy to the active servers. \nSo in case there is a need to link more servers to the proxy, it is simply necessary to add the address and port number of the server \nto the YAML file and reload the webpage, in this way the proxy will automatically include the new server between the choices for the target servers.\n\n```javascript\n//The \"/parseYaml\" endpoint is called only once, on the applications first render.\n//It parses the \"config.yaml\" to obtain addresses and ports of the servers.\nproxyApp.get(\"/parseYaml\", (req, res) =\u003e {\n  try {\n    targets = [];\n    let data = yaml.load(fs.readFileSync(\"./config.yaml\", \"utf8\"));\n    data.proxy.services[0].hosts.forEach((host) =\u003e {\n      targets.push(`http://${host.address}:${host.port}`);\n    });\n    return res.status(200).json({ data });\n  } catch (e) {\n    console.log(e);\n  }\n});\n```\n\nReactJs was used for the frontend, just to simplify dynamic rendering, and Material-UI as the UI framework for quick-to-use table.\nWhat follows is the list of the libraries that have been used for the assignment:\n\n- axios v.0.21.1\n- cors v.2.8.5\n- express v.4.17.1\n- http-proxy v.1.18.1\n- js-yaml v.4.1.0\n- node-cache v.5.1.2\n- react v.17.0.2\n- nodemon v.2.0.12\n- node-http-proxy-json v.0.1.9\n\n## Testing\nTo test the software, 2 libraries were used: \"jest\" and \"supertest\". Tests were done on the return values the servers are supposed to give, \nand on the status codes and headers expected. There are tests for the reverse-proxy file, and the on the 2 servers.\n\n\n\nUseful links:\n\nServer Cache in Node Js – YouTube https://www.youtube.com/watch?v=ipIGWZwxC7w\u0026t=499s\n\nhttp-proxy – Github Repository https://github.com/http-party/node-http-proxy#readme\n\nMock API - https://jsonplaceholder.typicode.com\n\nMeasuring response time: https://dzone.com/articles/properly-measuring-http-request-time-with-nodejs\n\nJest - https://jestjs.io\n\nSupertest - https://github.com/visionmedia/supertest#readme\n\nnode-http-proxy-json - https://github.com/langjt/node-http-proxy-json#readme\n\n\n\n## Author:\n### Jason Shuyinta\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjazzshu%2Freverse-proxy","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fjazzshu%2Freverse-proxy","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjazzshu%2Freverse-proxy/lists"}