-
Notifications
You must be signed in to change notification settings - Fork 213
Description
There’s an issue with the hxcpp/hashlink/neko socket code platform-specific socket requests are either not being processed at all, or the waitForRead / accept calls aren’t being triggered.
Note: If you are having cors problems, try again using the plugin below.
https://chromewebstore.google.com/detail/allow-cors-access-control/lhobafahddgcelffkeicbaginigeejlf
POC
Server Side
import sys.net.Host;
import sys.net.Socket;
import sys.thread.Thread;
import haxe.io.Bytes;
import haxe.io.Input;
using StringTools;
class Main {
#if cpp
private static var threadPool = new sys.thread.FixedThreadPool(32);
#end
public static function main() {
var listener_socket = new Socket();
// Binding 0.0.0.0 means, listen on "any / all IP addresses on this host"
listener_socket.bind(new Host('0.0.0.0'), 8000);
listener_socket.listen(9999); // big max connections
while (true) {
// Accepting socket
trace('waiting to accept...');
var peer_connection:Socket = listener_socket.accept();
if (peer_connection != null) {
trace('got connection from : ' + peer_connection.peer());
// Spawn a reader thread for this connection:
#if cpp threadPool.run #else Thread.create #end (() -> read(peer_connection));
}
}
}
static function read(peer_connection:Socket):Void {
try {
var rawRequest:String = parseRequestFromInputProtocol(peer_connection.input).toString();
var request = parseRequest(rawRequest);
var requestPath:String = request.path;
trace(requestPath);
if(requestPath.startsWith("/list"))
handleUploadList(peer_connection);
else if(requestPath.startsWith("/upload"))
handleUpload(peer_connection);
} catch(e) {
trace(e);
trace(e.stack);
}
peer_connection.close();
}
static function handleUploadList(peer_connection:Socket):Void {
var chunks:Array<Dynamic> = [];
for(i in 0...2) {
chunks.push({start: 0, end: 16777216, key: 'chunk_${Math.random()}', createdAt: 0});
}
peer_connection.output.writeString('HTTP/1.1 200 OK\r\nContent-Type: application/json\r\nConnection: close\r\n\r\n${haxe.Json.stringify({chunks: chunks})}');
}
static function handleUpload(peer_connection:Socket):Void {
trace(readUntilDelimiter(peer_connection.input));
try {
peer_connection.input.read(16777216);
} catch(e) {
}
peer_connection.output.writeString('HTTP/1.1 200 OK\r\nContent-Type: application/json\r\nConnection: close\r\n\r\n{"status":"success","sessionKey":"aWHdCaOsLwnwc90kEjfenp7UvxnVfwAf"}');
}
static function parseRequest(rawRequest:String):Dynamic {
var lines = rawRequest.split("\r\n");
var requestLine = lines[0].split(" ");
#if debug
trace(requestLine);
#end
var method = requestLine[0];
var path = requestLine[1].urlDecode();
return {method: method, path: path};
}
static function readUntilDelimiter(input:haxe.io.Input):String {
var buffer = new StringBuf();
var lastFourChars = "";
try {
while (true) {
var char = String.fromCharCode(input.readByte());
buffer.add(char);
// Keep track of the last 4 characters
lastFourChars += char;
if (lastFourChars.length > 4) {
lastFourChars = lastFourChars.substr(lastFourChars.length - 4);
}
// Check if we've found the delimiter
if (lastFourChars == "\r\n\r\n") {
break;
}
}
} catch (e:Dynamic) {
trace("Error reading from input: " + e);
}
return buffer.toString();
}
static function parseRequestFromInputProtocol(input:Input):Bytes
{
var httpRequestEnd = [0x0D, 0x0A, 0x0D, 0x0A];
#if cpp
var buffer:Array<cpp.UInt8> = new Array<cpp.UInt8>();
#elseif java
var buffer:Array<java.lang.Byte> = new Array<java.lang.Byte>();
#else
var buffer:Array<Int> = new Array<Int>();
#end
var index:Int = 0;
while (true)
{
var found:Bool = false;
buffer[index] = input.readByte();
if (index >= 4)
{
found = true;
for(i in 0...4)
{
if(buffer[index - 3 + i] != httpRequestEnd[i])
{
found = false;
break;
}
}
}
index++;
if(found)
break;
}
buffer.resize(buffer.length - httpRequestEnd.length);
#if cpp
return Bytes.ofData(buffer);
#else
var bytes = Bytes.alloc(buffer.length);
for(i in 0...buffer.length)
{
bytes.set(i, cast buffer[i]);
}
return bytes;
#end
}
}Client Side
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>List → Upload Loop (Real-Time Log + Continue)</title>
<style>
body{font-family:monospace;background:#0e1117;color:#ddd;margin:0;padding:10px}
#controls{margin-bottom:10px}
button{background:#1b4bff;border:none;color:#fff;padding:6px 12px;
border-radius:6px;font-weight:bold;cursor:pointer}
#log{font-size:13px;white-space:pre-wrap}
.ok{color:#7CFC00}
.err{color:#FF6347}
.list{color:#00BFFF}
.upload{color:#FFD700}
.info{color:#aaa}
</style>
</head>
<body>
<div id="controls">
<button id="continueBtn">Trigger Socket Select</button>
</div>
<h3>Real-Time List / Upload Log</h3>
<div id="log"></div>
<script>
const API_URL = "http://127.0.0.1:8000";
const CHUNK_SIZE = 16777216;
let id = 0;
let entries = [];
function render() {
const now = performance.now();
const html = entries.slice(-100).reverse().map(e=>{
const elapsed = (now - e.start).toFixed(0);
const dur = e.end ? `${e.duration} ms` : `${elapsed} ms`;
const cls = e.type==="list"?"list":(e.ok===false?"err":(e.type==="continue"?"info":"upload"));
const status = e.end ? (e.ok?"OK":"ERROR") : "•••";
return `<span class="${cls}">[${e.type.toUpperCase()}] ${status} ${dur} ${e.msg}</span>`;
}).join("\n");
document.getElementById("log").innerHTML = html;
requestAnimationFrame(render);
}
function addEntry(type,msg){
const e={type,msg,start:performance.now(),end:0,ok:null,duration:0};
entries.push(e);
if(entries.length>200) entries.shift();
return e;
}
function finishEntry(e,ok){
e.ok=ok;
e.end=performance.now();
e.duration=(e.end-e.start).toFixed(0);
}
async function getList(){
const ent=addEntry("list","→ /list?id="+id);
try{
const res=await fetch(`${API_URL}/list?id=${id++}`,{method:"POST"});
if(!res.ok) throw new Error(res.status);
const data=await res.json();
finishEntry(ent,true);
if(data.chunks?.length){
await Promise.all(data.chunks.map(c=>uploadChunk(c.key)));
}
getList();
}catch(e){
finishEntry(ent,false);
getList();
}
}
async function uploadChunk(key){
const ent=addEntry("upload","→ "+key);
const blob=new Blob([new Uint8Array(CHUNK_SIZE)],{type:"application/octet-stream"});
const formData=new FormData();
formData.append("file",blob);
try{
const res=await fetch(`${API_URL}/upload?chunkKey=${key}`,{method:"POST",body:formData});
if(!res.ok) throw new Error(res.status);
finishEntry(ent,true);
}catch(e){
finishEntry(ent,false);
}
}
// --- Continue button handler ---
document.getElementById("continueBtn").addEventListener("click", async ()=>{
const ent = addEntry("continue","→ /");
try {
const res = await fetch(`${API_URL || ''}/`, {method:"GET"});
if (!res.ok) throw new Error(res.status);
finishEntry(ent,true);
} catch (e) {
finishEntry(ent,false);
}
});
// start rendering and looping
render();
getList();
</script>
</body>
</html>Backend Behavior
Windows
HTTP requests usually fail.
Linux & FreeBSD
HTTP requests usually don’t fail, but sometimes a connection isn’t processed until another connection triggers it. I think the socket select call gets stuck at some point, and the connection isn’t handled until a new one arrives.
When a request freezes, pressing the “Trigger Socket Select” button will cause the frozen request to be processed.
I think the problem is related to Chrome’s TCP pooling feature.
MacOS
Working without any issue