File size: 7,412 Bytes
a1e3a8f | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 | // 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;
}
|