dosmqtt / src /g.cpp
HelloSun's picture
Add DOS x Gradio demo: G.EXE asks a Gradio Space over HTTP; update dev package
a1e3a8f verified
Raw History Blame Contribute Delete
7.41 kB
// G.EXE - ask a Gradio Space one question, over plain HTTP, from DOS.
//
// G <question words...>
// environment: GRADIO_HOST (default tencent-hy3.hf.space)
//
// It speaks the documented Gradio HTTP API itself:
// 1) POST /gradio_api/call/chat {"data":[...9 parameters...]} -> {"event_id":"..."}
// 2) GET /gradio_api/call/chat/<event_id> -> server-sent events
// ("generating" frames while the model streams, then one "complete" frame)
// DOS has no TLS, so the page's bridge (gradio-bridge.js) forwards these requests over HTTPS.
#include "mqtt.h"
#define DEFAULT_HOST "tencent-hy3.hf.space"
#define SYSTEM_PROMPT "Answer in plain ASCII English in at most 40 words."
#define LINE_MAX_LEN 2000
// The emulator's TCP sends 1460-byte segments back to back, ignores the receive window and never
// retransmits, so a segment that does not fit in our buffer is lost for good and the connection hangs.
// Keep the receive buffer several segments large and drain it quickly.
#define RECV_BUF 8192
static char line[LINE_MAX_LEN];
static int lineLen = 0;
static char finalData[LINE_MAX_LEN];
static char evt[16];
static int haveFinal = 0, sawError = 0, frames = 0;
static char statusLine[48];
static int statusDone = 0;
static char resp[1024];
static int respLen = 0;
static void handleLine( void ) {
if ( !statusDone ) { // first line of an HTTP response
strncpy( statusLine, line, sizeof( statusLine ) - 1 );
statusDone = 1;
return;
}
if ( strncmp( line, "event: ", 7 ) == 0 ) {
strncpy( evt, line + 7, sizeof( evt ) - 1 );
evt[sizeof( evt ) - 1] = 0;
if ( strcmp( evt, "generating" ) == 0 ) { frames++; printf( "." ); fflush( stdout ); }
if ( strcmp( evt, "error" ) == 0 ) sawError = 1;
}
else if ( strncmp( line, "data: ", 6 ) == 0 && strcmp( evt, "complete" ) == 0 ) {
strcpy( finalData, line + 6 );
haveFinal = 1;
}
}
static void feed( const uint8_t *p, int n, int sse ) {
for ( int i = 0; i < n; i++ ) {
if ( !sse ) { if ( respLen < (int)sizeof( resp ) - 1 ) resp[respLen++] = (char)p[i]; continue; }
char c = (char)p[i];
if ( c == '\n' ) { line[lineLen] = 0; handleLine( ); lineLen = 0; }
else if ( c != '\r' && lineLen < LINE_MAX_LEN - 1 ) line[lineLen++] = c;
}
}
static uint16_t portCounter = 0;
// One HTTP request/response on a fresh TCP connection. The response is read until the server closes.
static int httpExchange( IpAddr_t addr, const char *req, int sse, uint32_t timeoutSec ) {
TcpSocket *s = TcpSocketMgr::getSocket( );
if ( s->setRecvBuffer( RECV_BUF ) ) { printf( "TCP: no memory for the receive buffer\n" ); TcpSocketMgr::freeSocket( s ); return -1; }
portCounter += 17;
if ( s->connect( (uint16_t)( pseudoPort( ) + portCounter ), addr, 80, 10000 ) != 0 ) {
TcpSocketMgr::freeSocket( s );
printf( "TCP: connect to port 80 failed\n" );
return -1;
}
lineLen = 0; respLen = 0; statusDone = 0; statusLine[0] = 0; evt[0] = 0;
s->send( (uint8_t *)req, (uint16_t)strlen( req ) );
static uint8_t buf[1024];
clockTicks_t start = TIMER_GET_CURRENT( );
int closed = 0, rc = 0;
while ( !CtrlBreakDetected ) {
Utils::processPackets( );
int16_t r = s->recv( buf, sizeof( buf ) );
if ( r > 0 ) { feed( buf, r, sse ); continue; }
if ( closed ) break;
if ( s->isRemoteClosed( ) ) { closed = 1; continue; } // one more pass to drain the buffer
if ( Timer_diff( start, TIMER_GET_CURRENT( ) ) > TIMER_SECS_TO_TICKS( timeoutSec ) ) {
printf( "\nHTTP: timed out after %lu s\n", (unsigned long)timeoutSec ); rc = -2; break;
}
}
s->close( );
TcpSocketMgr::freeSocket( s );
if ( sse && lineLen > 0 ) { line[lineLen] = 0; handleLine( ); }
return rc;
}
// Decode the first JSON string that follows `[["` in the SSE data line and print it word wrapped.
static void printAnswer( const char *data ) {
const char *p = strstr( data, "[[\"" );
if ( !p ) { printf( "A> (no text in reply: %.60s)\n", data ); return; }
p += 3;
char out[1200]; int n = 0;
while ( *p && n < (int)sizeof( out ) - 2 ) {
if ( *p == '"' ) break;
if ( *p == '\\' ) {
p++;
if ( *p == 'n' ) out[n++] = ' ';
else if ( *p == 'u' ) {
unsigned v = 0; int k;
for ( k = 1; k <= 4 && p[k]; k++ ) { char h = p[k]; v = v * 16 + ( h <= '9' ? h - '0' : ( h | 32 ) - 'a' + 10 ); }
out[n++] = ( v >= 32 && v < 127 ) ? (char)v : '?';
p += 4;
}
else out[n++] = *p;
if ( *p ) p++;
continue;
}
out[n++] = *p++;
}
out[n] = 0;
printf( "A> " );
int col = 3;
for ( char *w = strtok( out, " " ); w; w = strtok( NULL, " " ) ) {
int l = (int)strlen( w );
if ( col + l + 1 > 78 ) { printf( "\n " ); col = 3; }
printf( "%s ", w ); col += l + 1;
}
printf( "\n" );
}
int main( int argc, char *argv[] ) {
char question[160]; question[0] = 0;
for ( int i = 1; i < argc; i++ ) {
if ( strlen( question ) + strlen( argv[i] ) + 2 > sizeof( question ) ) break;
if ( i > 1 ) strcat( question, " " );
strcat( question, argv[i] );
}
for ( char *q = question; *q; q++ ) if ( *q < 32 || *q > 126 || *q == '"' || *q == '\\' ) *q = ' ';
if ( !question[0] ) { printf( "Usage: G <question>\n env GRADIO_HOST (default %s)\n", DEFAULT_HOST ); return 1; }
const char *host = getenv( "GRADIO_HOST" );
if ( !host || !*host ) host = DEFAULT_HOST;
printf( "G.EXE - asking Gradio Space %s\nQ> %s\n", host, question );
if ( netInit( ) ) { printf( "TCP/IP init failed (is the packet driver loaded?)\n" ); return 2; }
IpAddr_t addr;
printf( "DNS: resolving %s ...\n", host );
if ( resolveHost( host, addr ) ) { printf( "DNS: failed\n" ); netShutdown( 3 ); }
printf( "DNS: %d.%d.%d.%d\n", addr[0], addr[1], addr[2], addr[3] );
clockTicks_t t0 = TIMER_GET_CURRENT( );
char body[400], req[700], now[16];
sprintf( body, "{\"data\":[\"%s\",\"%s\",[],\"no_think\",null,0,0,null,\"\"]}", question, SYSTEM_PROMPT );
sprintf( req, "POST /gradio_api/call/chat HTTP/1.0\r\nHost: %s\r\nContent-Type: application/json\r\nContent-Length: %d\r\nConnection: close\r\n\r\n%s",
host, (int)strlen( body ), body );
stamp( now );
printf( "[%s] POST /gradio_api/call/chat (%d bytes)\n", now, (int)strlen( body ) );
if ( httpExchange( addr, req, 0, 30 ) ) netShutdown( 4 );
resp[respLen] = 0;
printf( "HTTP: %.40s\n", resp );
char *e = strstr( resp, "\"event_id\":\"" );
if ( !e ) { printf( "No event_id in reply:\n%.300s\n", resp ); netShutdown( 5 ); }
e += 12;
char id[64]; int k = 0;
while ( *e && *e != '"' && k < 62 ) id[k++] = *e++;
id[k] = 0;
printf( "event_id: %s\n", id );
sprintf( req, "GET /gradio_api/call/chat/%s HTTP/1.0\r\nHost: %s\r\nAccept: text/event-stream\r\nConnection: close\r\n\r\n", id, host );
stamp( now );
printf( "[%s] GET /gradio_api/call/chat/<event_id> (waiting for the model) ", now );
fflush( stdout );
if ( httpExchange( addr, req, 1, 150 ) ) netShutdown( 6 );
printf( "\n" );
stamp( now );
if ( !haveFinal ) { printf( "[%s] No answer received (%s, events: %s)\n", now, statusLine, sawError ? "error" : "none" ); netShutdown( 7 ); }
unsigned long secs = ( TIMER_GET_CURRENT( ) - t0 ) / 18;
printf( "[%s] %d streaming frames, done in %lu s\n", now, frames, secs );
printAnswer( finalData );
netShutdown( 0 );
return 0;
}