mirror of
https://github.com/dalbodeule/hop-gate.git
synced 2026-09-21 08:11:06 +09:00
docs: align architecture and API docs with Go 1.27 ingress support
- document HTTP/1.1, HTTP/2, and HTTP/3 ingress - describe SSE streaming and timeout behavior - document HTTP/2 and HTTP/3 Extended CONNECT - update yamux stream and deployment architecture - document Go 1.27 and UDP HTTP/3 requirements
This commit is contained in:
@@ -6,14 +6,17 @@ import (
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/dalbodeule/hop-gate/internal/config"
|
||||
"github.com/dalbodeule/hop-gate/internal/logging"
|
||||
"github.com/dalbodeule/hop-gate/internal/tunnel"
|
||||
"github.com/gorilla/websocket"
|
||||
)
|
||||
|
||||
func runYamuxTunnelClient(ctx context.Context, logger logging.Logger, cfg *config.ClientConfig) error {
|
||||
@@ -62,12 +65,20 @@ func runYamuxTunnelClient(ctx context.Context, logger logging.Logger, cfg *confi
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
go handleYamuxHTTPStream(stream, client, localBase, logger)
|
||||
go handleYamuxHTTPStream(ctx, stream, client, localBase, logger)
|
||||
}
|
||||
}
|
||||
|
||||
func handleYamuxHTTPStream(stream *tunnel.Stream, client *http.Client, localBase *url.URL, logger logging.Logger) {
|
||||
func handleYamuxHTTPStream(ctx context.Context, stream *tunnel.Stream, client *http.Client, localBase *url.URL, logger logging.Logger) {
|
||||
defer stream.Close()
|
||||
if stream.Meta.Kind == "websocket" {
|
||||
handleYamuxWebSocketStream(ctx, stream, localBase, logger)
|
||||
return
|
||||
}
|
||||
if stream.Meta.Kind != "http" {
|
||||
logger.Warn("unsupported yamux stream kind", logging.Fields{"kind": stream.Meta.Kind})
|
||||
return
|
||||
}
|
||||
request, err := http.ReadRequest(bufio.NewReader(stream))
|
||||
if err != nil {
|
||||
logger.Warn("read HTTP request from yamux stream failed", logging.Fields{"error": err.Error()})
|
||||
@@ -94,7 +105,73 @@ func handleYamuxHTTPStream(stream *tunnel.Stream, client *http.Client, localBase
|
||||
return
|
||||
}
|
||||
defer response.Body.Close()
|
||||
if err := response.Write(stream); err != nil {
|
||||
if err := writeHTTPResponse(stream, response); err != nil {
|
||||
logger.Warn("write local HTTP response to yamux stream failed", logging.Fields{"error": err.Error()})
|
||||
}
|
||||
}
|
||||
|
||||
func writeHTTPResponse(stream io.Writer, response *http.Response) error {
|
||||
return response.Write(stream)
|
||||
}
|
||||
|
||||
func handleYamuxWebSocketStream(ctx context.Context, stream *tunnel.Stream, localBase *url.URL, logger logging.Logger) {
|
||||
request, err := http.ReadRequest(bufio.NewReader(stream))
|
||||
if err != nil {
|
||||
logger.Warn("read WebSocket request from yamux stream failed", logging.Fields{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
request.URL.Scheme = "ws"
|
||||
request.URL.Host = localBase.Host
|
||||
request.RequestURI = ""
|
||||
|
||||
header := make(http.Header)
|
||||
var subprotocols []string
|
||||
for key, values := range request.Header {
|
||||
switch http.CanonicalHeaderKey(key) {
|
||||
case "Connection", "Upgrade", "Sec-Websocket-Key", "Sec-Websocket-Version", "Sec-Websocket-Extensions":
|
||||
continue
|
||||
case "Sec-Websocket-Protocol":
|
||||
for _, value := range values {
|
||||
for _, protocol := range strings.Split(value, ",") {
|
||||
if strings.TrimSpace(protocol) != "" {
|
||||
subprotocols = append(subprotocols, strings.TrimSpace(protocol))
|
||||
}
|
||||
}
|
||||
}
|
||||
default:
|
||||
header[key] = append([]string(nil), values...)
|
||||
}
|
||||
}
|
||||
dialer := websocket.Dialer{Subprotocols: subprotocols, HandshakeTimeout: 10 * time.Second}
|
||||
backend, response, err := dialer.DialContext(ctx, request.URL.String(), header)
|
||||
if err != nil {
|
||||
logger.Warn("dial local WebSocket failed", logging.Fields{"error": err.Error()})
|
||||
failure := &http.Response{
|
||||
StatusCode: http.StatusBadGateway,
|
||||
Status: "502 Bad Gateway",
|
||||
ProtoMajor: 1,
|
||||
ProtoMinor: 1,
|
||||
Header: http.Header{"Content-Type": []string{"text/plain; charset=utf-8"}},
|
||||
Body: http.NoBody,
|
||||
}
|
||||
_ = failure.Write(stream)
|
||||
return
|
||||
}
|
||||
defer backend.Close()
|
||||
if err := response.Write(stream); err != nil {
|
||||
logger.Warn("write WebSocket handshake to server failed", logging.Fields{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
backendConn := backend.UnderlyingConn()
|
||||
result := make(chan error, 2)
|
||||
go func() {
|
||||
_, err := io.Copy(stream, backendConn)
|
||||
result <- err
|
||||
}()
|
||||
go func() {
|
||||
_, err := io.Copy(backendConn, stream)
|
||||
result <- err
|
||||
}()
|
||||
<-result
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user