Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commitd7bcc5b

Browse files
committed
wip
1 parent47fbba9 commitd7bcc5b

File tree

2 files changed

+70
-305
lines changed

2 files changed

+70
-305
lines changed

‎cli/exp_stdio_http.go‎

Lines changed: 49 additions & 88 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ package cli
33
import (
44
"bufio"
55
"context"
6-
"encoding/json"
76
"fmt"
87
"io"
98
"net"
@@ -30,9 +29,8 @@ func (r *RootCmd) stdioHTTPCommand() *serpent.Command {
3029
Use:"stdio-http <command> [args...]",
3130
Short:"Run command and expose stdin/stdout/stderr over HTTP",
3231
Long:`Start an HTTP server that runs a command and exposes its stdio streams:
33-
- POST requests to /stdin send data to the command's stdin
34-
- GET requests to /stdout stream the command's stdout as Server-Sent Events
35-
- GET requests to /stderr stream the command's stderr as Server-Sent Events`,
32+
- POST requests to / send data to the command's stdin
33+
- GET requests to / receive Server-Sent Events with stdout and stderr output`,
3634
Handler:func(inv*serpent.Invocation)error {
3735
iflen(inv.Args)==0 {
3836
returnxerrors.Errorf("command is required")
@@ -110,6 +108,8 @@ func handleStdioHTTP(inv *serpent.Invocation, cmdName string, cmdArgs []string,
110108
}
111109

112110
// Start the command
111+
cmdCtx,cmdCancel:=context.WithCancel(ctx)
112+
defercmdCancel()
113113
iferr:=server.startCommand(cmdName,cmdArgs);err!=nil {
114114
returnxerrors.Errorf("failed to start command: %w",err)
115115
}
@@ -123,9 +123,7 @@ func handleStdioHTTP(inv *serpent.Invocation, cmdName string, cmdArgs []string,
123123

124124
// Setup HTTP server
125125
mux:=http.NewServeMux()
126-
mux.HandleFunc("/stdin",server.handleStdin)
127-
mux.HandleFunc("/stdout",server.handleStdout)
128-
mux.HandleFunc("/stderr",server.handleStderr)
126+
mux.HandleFunc("/",server.handleRoot)
129127

130128
addr:=net.JoinHostPort(host,port)
131129
httpServer:=&http.Server{
@@ -136,9 +134,8 @@ func handleStdioHTTP(inv *serpent.Invocation, cmdName string, cmdArgs []string,
136134
cliui.Infof(inv.Stderr,"Starting HTTP server on http://%s",addr)
137135
cliui.Infof(inv.Stderr,"Command: %s %s",cmdName,strings.Join(cmdArgs," "))
138136
cliui.Infof(inv.Stderr,"Endpoints:")
139-
cliui.Infof(inv.Stderr," POST /stdin - Send data to command stdin")
140-
cliui.Infof(inv.Stderr," GET /stdout - Stream command stdout (SSE)")
141-
cliui.Infof(inv.Stderr," GET /stderr - Stream command stderr (SSE)")
137+
cliui.Infof(inv.Stderr," POST / - Send data to command stdin")
138+
cliui.Infof(inv.Stderr," GET / - Stream command output (SSE with stdout/stderr events)")
142139

143140
// Start HTTP server in goroutine
144141
errCh:=make(chanerror,1)
@@ -166,16 +163,16 @@ func handleStdioHTTP(inv *serpent.Invocation, cmdName string, cmdArgs []string,
166163

167164
// Wait for command to finish
168165
ifserver.cmd.Process!=nil {
169-
iferr:=server.cmd.Wait();err!=nil {
170-
cliui.Warnf(inv.Stderr,"Command finished witherror: %v",err)
166+
iferr:=server.cmd.();err!=nil {
167+
returnxerrors.Errorf("kill commanderror: %w",err)
171168
}
172169
}
173170

174171
returnnil
175172
}
176173

177-
func (s*stdioHTTPServer)startCommand(cmdNamestring,cmdArgs []string)error {
178-
s.cmd=exec.CommandContext(s.ctx,cmdName,cmdArgs...)
174+
func (s*stdioHTTPServer)startCommand(ctx context.Context,cmdNamestring,cmdArgs []string)error {
175+
s.cmd=exec.CommandContext(ctx,cmdName,cmdArgs...)
179176

180177
varerrerror
181178
s.stdin,err=s.cmd.StdinPipe()
@@ -285,94 +282,55 @@ func (s *stdioHTTPServer) distributeStderr() {
285282
}
286283
}
287284

288-
func (s*stdioHTTPServer)handleStdin(w http.ResponseWriter,r*http.Request) {
289-
ifr.Method!=http.MethodPost {
290-
http.Error(w,"Method not allowed",http.StatusMethodNotAllowed)
291-
return
292-
}
293-
294-
body,err:=io.ReadAll(r.Body)
295-
iferr!=nil {
296-
http.Error(w,"Failed to read body",http.StatusBadRequest)
297-
return
298-
}
299-
300-
ifs.stdin==nil {
301-
http.Error(w,"Command stdin not available",http.StatusServiceUnavailable)
302-
return
303-
}
304-
305-
_,err=s.stdin.Write(body)
306-
iferr!=nil {
307-
http.Error(w,"Failed to write to command stdin",http.StatusInternalServerError)
308-
return
309-
}
310-
311-
w.Header().Set("Content-Type","application/json")
312-
w.WriteHeader(http.StatusOK)
313-
json.NewEncoder(w).Encode(map[string]interface{}{
314-
"status":"ok",
315-
"bytes_written":len(body),
316-
})
317-
}
318-
319-
func (s*stdioHTTPServer)handleStdout(w http.ResponseWriter,r*http.Request) {
320-
ifr.Method!=http.MethodGet {
321-
http.Error(w,"Method not allowed",http.StatusMethodNotAllowed)
322-
return
323-
}
324-
325-
s.setupSSE(w)
326-
327-
ch:=make(chan []byte,10)
328-
s.mu.Lock()
329-
s.stdoutSubscribers[ch]=true
330-
s.mu.Unlock()
331-
332-
deferfunc() {
333-
s.mu.Lock()
334-
delete(s.stdoutSubscribers,ch)
335-
s.mu.Unlock()
336-
close(ch)
337-
}()
338-
339-
flusher,ok:=w.(http.Flusher)
340-
if!ok {
341-
http.Error(w,"Streaming not supported",http.StatusInternalServerError)
342-
return
343-
}
285+
func (s*stdioHTTPServer)handleRoot(w http.ResponseWriter,r*http.Request) {
286+
switchr.Method {
287+
casehttp.MethodPost:
288+
// Read stdin data first
289+
body,err:=io.ReadAll(r.Body)
290+
iferr!=nil {
291+
http.Error(w,"Failed to read body",http.StatusBadRequest)
292+
return
293+
}
344294

345-
for {
346-
select {
347-
casedata:=<-ch:
348-
fmt.Fprintf(w,"data: %s\n\n",string(data))
349-
flusher.Flush()
350-
case<-r.Context().Done():
295+
ifs.stdin==nil {
296+
http.Error(w,"Command stdin not available",http.StatusServiceUnavailable)
351297
return
352-
case<-s.ctx.Done():
298+
}
299+
300+
// Write to stdin
301+
_,err=s.stdin.Write(body)
302+
iferr!=nil {
303+
http.Error(w,"Failed to write to command stdin",http.StatusInternalServerError)
353304
return
354305
}
355-
}
356-
}
357306

358-
func (s*stdioHTTPServer)handleStderr(w http.ResponseWriter,r*http.Request) {
359-
ifr.Method!=http.MethodGet {
307+
// Start streaming SSE
308+
s.handleStream(w,r)
309+
casehttp.MethodGet:
310+
s.handleStream(w,r)
311+
default:
360312
http.Error(w,"Method not allowed",http.StatusMethodNotAllowed)
361-
return
362313
}
314+
}
363315

316+
func (s*stdioHTTPServer)handleStream(w http.ResponseWriter,r*http.Request) {
364317
s.setupSSE(w)
365318

366-
ch:=make(chan []byte,10)
319+
stdoutCh:=make(chan []byte,10)
320+
stderrCh:=make(chan []byte,10)
321+
367322
s.mu.Lock()
368-
s.stderrSubscribers[ch]=true
323+
s.stdoutSubscribers[stdoutCh]=true
324+
s.stderrSubscribers[stderrCh]=true
369325
s.mu.Unlock()
370326

371327
deferfunc() {
372328
s.mu.Lock()
373-
delete(s.stderrSubscribers,ch)
329+
delete(s.stdoutSubscribers,stdoutCh)
330+
delete(s.stderrSubscribers,stderrCh)
374331
s.mu.Unlock()
375-
close(ch)
332+
close(stdoutCh)
333+
close(stderrCh)
376334
}()
377335

378336
flusher,ok:=w.(http.Flusher)
@@ -383,8 +341,11 @@ func (s *stdioHTTPServer) handleStderr(w http.ResponseWriter, r *http.Request) {
383341

384342
for {
385343
select {
386-
casedata:=<-ch:
387-
fmt.Fprintf(w,"data: %s\n\n",string(data))
344+
casedata:=<-stdoutCh:
345+
fmt.Fprintf(w,"event: stdout\ndata: %s\n\n",string(data))
346+
flusher.Flush()
347+
casedata:=<-stderrCh:
348+
fmt.Fprintf(w,"event: stderr\ndata: %s\n\n",string(data))
388349
flusher.Flush()
389350
case<-r.Context().Done():
390351
return

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp