Created
September 13, 2022 18:00
-
-
Save snydergd/0bef6fedd035ad21d383bde31145c9a7 to your computer and use it in GitHub Desktop.
Single-file zero-dependency nodejs web-based TCP client
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
const http = require('http'); | |
const tls = require('tls') | |
const net = require('net'); | |
function setupTls({host, port}) { | |
return new Promise((resolve) => { | |
const socket = tls.connect( | |
{ | |
port: port, | |
host: host, | |
servername: host, // for SNI | |
}, | |
() => { | |
resolve(socket) | |
} | |
) | |
}); | |
} | |
function setupPlain({host, port}) { | |
return new Promise(resolve => { | |
const socket = new net.Socket(); | |
socket.on('connect', () => { | |
resolve(socket); | |
}); | |
socket.connect(port, host); | |
}); | |
} | |
function exchange({dataCallback, endCallback, isTls=false, body, ...rest}) { | |
(isTls ? setupTls : setupPlain)({...rest, port: rest.port || (isTls?443:80)}).then(socket => { | |
socket.on('close', endCallback) | |
socket.on('data', dataCallback) | |
socket.on('error', (error) => { | |
console.log('Error', error) | |
}) | |
socket.write(body) // send the HTTP request | |
}); | |
} | |
const pageContent = `<!DOCTYPE html> | |
<html> | |
<head> | |
<meta charset="utf-8"> | |
<title>Raw HTTP client</title> | |
<style> | |
textarea { | |
display: block; | |
width: 100%; | |
height: 20em; | |
} | |
input[type='text'] { | |
width: 100%; | |
display: block; | |
} | |
</style> | |
</head> | |
<body> | |
<h2>Raw HTTP client</h2> | |
<p>Host</p> | |
<input type="text" id="host" value="" /> | |
<p>Request</p> | |
<textarea id="request"></textarea> | |
<button type="button" onclick="send()">Send</button> | |
<p>Response</p> | |
<textarea id="response" disabled></textarea> | |
<script> | |
function send() { | |
const host = document.querySelector("#host"); | |
const input = document.querySelector("#request"); | |
const output = document.querySelector("#response"); | |
const hostPort = host.value.split(":"); | |
fetch("/api", { | |
method: "POST", | |
body: JSON.stringify({ | |
host: hostPort[0], | |
port: (hostPort.length > 1) ? parseInt(hostPort[1]) : false, | |
body: input.value, | |
}), | |
}).then(async resp => { | |
output.value = ""; | |
const reader = resp.body.pipeThrough(new TextDecoderStream()).getReader(); | |
while (true) { | |
const {value, done} = await reader.read(); | |
if (done) break; | |
output.value += value; | |
} | |
}); | |
} | |
</script> | |
</body> | |
<html> | |
`; | |
const requestListener = function (req, res) { | |
console.log(req.url); | |
if (req.url === "/api") { | |
let requestData = ''; | |
req.on('data', chunk => requestData += chunk); | |
req.on('end', () => { | |
const requestInfo = JSON.parse(requestData); | |
res.writeHead(200, 'OK', { | |
"Content-Type": "text/plain", | |
}); | |
exchange({ | |
...requestInfo, | |
dataCallback: data => { | |
res.write(data); | |
console.log(".") | |
}, | |
endCallback: res.end, | |
}) | |
}); | |
} else { | |
res.writeHead(200, 'OK', { | |
"Content-Type": "text/html", | |
}); | |
res.end(pageContent); | |
} | |
} | |
const server = http.createServer(requestListener); | |
const port = 8084; | |
server.listen(port); | |
console.log(`listening on ${port}`); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment