mirror of
https://github.com/dalbodeule/hop-gate.git
synced 2026-02-05 00:02:23 +09:00
Compare commits
3 Commits
34bf0eed98
...
99be2d2e31
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
99be2d2e31 | ||
|
|
1fa5e900f8 | ||
|
|
bf5c3c8f59 |
18
Makefile
18
Makefile
@@ -66,3 +66,21 @@ docker-server:
|
|||||||
@echo "Building server Docker image..."
|
@echo "Building server Docker image..."
|
||||||
docker build -f Dockerfile.server -t hop-gate-server:$(VERSION) .
|
docker build -f Dockerfile.server -t hop-gate-server:$(VERSION) .
|
||||||
|
|
||||||
|
# --- Protobuf code generation -------------------------------------------------
|
||||||
|
# Requires:
|
||||||
|
# - protoc (https://grpc.io/docs/protoc-installation/)
|
||||||
|
# - protoc-gen-go (go install google.golang.org/protobuf/cmd/protoc-gen-go@latest)
|
||||||
|
#
|
||||||
|
# Generates Go types under internal/protocol/pb from internal/protocol/hopgate_stream.proto.
|
||||||
|
# NOTE:
|
||||||
|
# - go_package in hopgate_stream.proto is set to:
|
||||||
|
# github.com/dalbodeule/hop-gate/internal/protocol/pb;protocolpb
|
||||||
|
# - With --go_out=. (without paths=source_relative), protoc will place the
|
||||||
|
# generated file under internal/protocol/pb according to go_package.
|
||||||
|
proto:
|
||||||
|
@echo "Generating Go code from Protobuf schemas..."
|
||||||
|
protoc \
|
||||||
|
--go_out=. \
|
||||||
|
internal/protocol/hopgate_stream.proto
|
||||||
|
@echo "Protobuf generation completed."
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,8 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bufio"
|
|
||||||
"context"
|
"context"
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
stdfs "io/fs"
|
stdfs "io/fs"
|
||||||
@@ -208,8 +206,7 @@ func (w *dtlsSessionWrapper) ForwardHTTP(ctx context.Context, logger logging.Log
|
|||||||
HTTPRequest: httpReq,
|
HTTPRequest: httpReq,
|
||||||
}
|
}
|
||||||
|
|
||||||
enc := json.NewEncoder(w.sess)
|
if err := protocol.DefaultCodec.Encode(w.sess, env); err != nil {
|
||||||
if err := enc.Encode(env); err != nil {
|
|
||||||
log.Error("failed to encode http envelope", logging.Fields{
|
log.Error("failed to encode http envelope", logging.Fields{
|
||||||
"error": err.Error(),
|
"error": err.Error(),
|
||||||
})
|
})
|
||||||
@@ -218,16 +215,7 @@ func (w *dtlsSessionWrapper) ForwardHTTP(ctx context.Context, logger logging.Log
|
|||||||
|
|
||||||
// 클라이언트로부터 HTTP 응답 Envelope 를 수신합니다.
|
// 클라이언트로부터 HTTP 응답 Envelope 를 수신합니다.
|
||||||
var respEnv protocol.Envelope
|
var respEnv protocol.Envelope
|
||||||
|
if err := protocol.DefaultCodec.Decode(w.sess, &respEnv); err != nil {
|
||||||
// NOTE: pion/dtls 는 복호화된 애플리케이션 데이터를 호출자가 제공한 버퍼에 채웁니다.
|
|
||||||
// 기본 JSON 디코더 버퍼만 사용하면 큰 HTTP 응답/Envelope 에서 "dtls: buffer too small"
|
|
||||||
// 오류가 발생할 수 있으므로, 충분히 큰 bufio.Reader(64KiB)를 사용합니다. (ko)
|
|
||||||
// NOTE: pion/dtls decrypts application data into the buffer provided by the caller.
|
|
||||||
// Using only the default JSON decoder buffer can cause "dtls: buffer too small"
|
|
||||||
// errors for large HTTP responses/envelopes, so we wrap the session with a
|
|
||||||
// reasonably large bufio.Reader (64KiB). (en)
|
|
||||||
dec := json.NewDecoder(bufio.NewReaderSize(w.sess, 64*1024))
|
|
||||||
if err := dec.Decode(&respEnv); err != nil {
|
|
||||||
log.Error("failed to decode http envelope", logging.Fields{
|
log.Error("failed to decode http envelope", logging.Fields{
|
||||||
"error": err.Error(),
|
"error": err.Error(),
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,2 +1,2 @@
|
|||||||
/*! tailwindcss v4.1.17 | MIT License | https://tailwindcss.com */
|
/*! tailwindcss v4.1.17 | MIT License | https://tailwindcss.com */
|
||||||
@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-tracking:initial;--tw-blur:initial;--tw-brightness:initial;--tw-contrast:initial;--tw-grayscale:initial;--tw-hue-rotate:initial;--tw-invert:initial;--tw-opacity:initial;--tw-saturate:initial;--tw-sepia:initial;--tw-drop-shadow:initial;--tw-drop-shadow-color:initial;--tw-drop-shadow-alpha:100%;--tw-drop-shadow-size:initial}}}.visible{visibility:visible}.absolute{position:absolute}.fixed{position:fixed}.static{position:static}.contents{display:contents}.flex{display:flex}.inline-flex{display:inline-flex}.table{display:table}.min-h-screen{min-height:100vh}.w-\[240px\]{width:240px}.w-full{width:100%}.flex-col{flex-direction:column}.items-baseline{align-items:baseline}.items-center{align-items:center}.justify-center{justify-content:center}.text-center{text-align:center}.tracking-\[0\.25em\]{--tw-tracking:.25em;letter-spacing:.25em}.uppercase{text-transform:uppercase}.opacity-90{opacity:.9}.filter{filter:var(--tw-blur,)var(--tw-brightness,)var(--tw-contrast,)var(--tw-grayscale,)var(--tw-hue-rotate,)var(--tw-invert,)var(--tw-saturate,)var(--tw-sepia,)var(--tw-drop-shadow,)}@property --tw-tracking{syntax:"*";inherits:false}@property --tw-blur{syntax:"*";inherits:false}@property --tw-brightness{syntax:"*";inherits:false}@property --tw-contrast{syntax:"*";inherits:false}@property --tw-grayscale{syntax:"*";inherits:false}@property --tw-hue-rotate{syntax:"*";inherits:false}@property --tw-invert{syntax:"*";inherits:false}@property --tw-opacity{syntax:"*";inherits:false}@property --tw-saturate{syntax:"*";inherits:false}@property --tw-sepia{syntax:"*";inherits:false}@property --tw-drop-shadow{syntax:"*";inherits:false}@property --tw-drop-shadow-color{syntax:"*";inherits:false}@property --tw-drop-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-drop-shadow-size{syntax:"*";inherits:false}
|
@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-tracking:initial;--tw-blur:initial;--tw-brightness:initial;--tw-contrast:initial;--tw-grayscale:initial;--tw-hue-rotate:initial;--tw-invert:initial;--tw-opacity:initial;--tw-saturate:initial;--tw-sepia:initial;--tw-drop-shadow:initial;--tw-drop-shadow-color:initial;--tw-drop-shadow-alpha:100%;--tw-drop-shadow-size:initial}}}.visible{visibility:visible}.absolute{position:absolute}.fixed{position:fixed}.static{position:static}.container{width:100%}.contents{display:contents}.flex{display:flex}.inline-flex{display:inline-flex}.table{display:table}.min-h-screen{min-height:100vh}.w-\[240px\]{width:240px}.w-full{width:100%}.flex-col{flex-direction:column}.items-baseline{align-items:baseline}.items-center{align-items:center}.justify-center{justify-content:center}.text-center{text-align:center}.tracking-\[0\.25em\]{--tw-tracking:.25em;letter-spacing:.25em}.uppercase{text-transform:uppercase}.opacity-90{opacity:.9}.filter{filter:var(--tw-blur,)var(--tw-brightness,)var(--tw-contrast,)var(--tw-grayscale,)var(--tw-hue-rotate,)var(--tw-invert,)var(--tw-saturate,)var(--tw-sepia,)var(--tw-drop-shadow,)}@property --tw-tracking{syntax:"*";inherits:false}@property --tw-blur{syntax:"*";inherits:false}@property --tw-brightness{syntax:"*";inherits:false}@property --tw-contrast{syntax:"*";inherits:false}@property --tw-grayscale{syntax:"*";inherits:false}@property --tw-hue-rotate{syntax:"*";inherits:false}@property --tw-invert{syntax:"*";inherits:false}@property --tw-opacity{syntax:"*";inherits:false}@property --tw-saturate{syntax:"*";inherits:false}@property --tw-sepia{syntax:"*";inherits:false}@property --tw-drop-shadow{syntax:"*";inherits:false}@property --tw-drop-shadow-color{syntax:"*";inherits:false}@property --tw-drop-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-drop-shadow-size{syntax:"*";inherits:false}
|
||||||
229
internal/protocol/codec.go
Normal file
229
internal/protocol/codec.go
Normal file
@@ -0,0 +1,229 @@
|
|||||||
|
package protocol
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"encoding/binary"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
|
||||||
|
protocolpb "github.com/dalbodeule/hop-gate/internal/protocol/pb"
|
||||||
|
"google.golang.org/protobuf/proto"
|
||||||
|
)
|
||||||
|
|
||||||
|
// defaultDecoderBufferSize 는 pion/dtls 가 복호화한 애플리케이션 데이터를
|
||||||
|
// JSON 디코더가 안전하게 처리할 수 있도록 사용하는 버퍼 크기입니다.
|
||||||
|
// This matches existing 64KiB readers used around DTLS sessions (used by the JSON codec).
|
||||||
|
const defaultDecoderBufferSize = 64 * 1024
|
||||||
|
|
||||||
|
// maxProtoEnvelopeBytes 는 단일 Protobuf Envelope 의 최대 크기에 대한 보수적 상한입니다.
|
||||||
|
// 아직 하드 리미트로 사용하지는 않지만, 향후 방어적 체크에 사용할 수 있습니다.
|
||||||
|
const maxProtoEnvelopeBytes = 512 * 1024 // 512KiB, 충분히 여유 있는 값
|
||||||
|
|
||||||
|
// WireCodec 는 protocol.Envelope 의 직렬화/역직렬화를 추상화합니다.
|
||||||
|
// JSON, Protobuf, length-prefixed binary 등으로 교체할 때 이 인터페이스만 유지하면 됩니다.
|
||||||
|
type WireCodec interface {
|
||||||
|
Encode(w io.Writer, env *Envelope) error
|
||||||
|
Decode(r io.Reader, env *Envelope) error
|
||||||
|
}
|
||||||
|
|
||||||
|
// jsonCodec 은 JSON 기반 WireCodec 구현입니다.
|
||||||
|
// JSON 직렬화를 계속 사용하고 싶을 때를 위해 남겨둡니다.
|
||||||
|
type jsonCodec struct{}
|
||||||
|
|
||||||
|
// Encode 는 Envelope 를 JSON 으로 인코딩해 작성합니다.
|
||||||
|
// Encode encodes an Envelope as JSON to the given writer.
|
||||||
|
func (jsonCodec) Encode(w io.Writer, env *Envelope) error {
|
||||||
|
enc := json.NewEncoder(w)
|
||||||
|
return enc.Encode(env)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decode 는 DTLS 세션에서 읽은 데이터를 JSON Envelope 로 디코딩합니다.
|
||||||
|
// pion/dtls 의 버퍼 특성 때문에, 충분히 큰 bufio.Reader 로 감싸서 사용합니다.
|
||||||
|
// Decode decodes an Envelope from JSON using a buffered reader on top of the DTLS session.
|
||||||
|
func (jsonCodec) Decode(r io.Reader, env *Envelope) error {
|
||||||
|
dec := json.NewDecoder(bufio.NewReaderSize(r, defaultDecoderBufferSize))
|
||||||
|
return dec.Decode(env)
|
||||||
|
}
|
||||||
|
|
||||||
|
// protobufCodec 은 Protobuf + length-prefix framing 기반 WireCodec 구현입니다.
|
||||||
|
// 한 Envelope 당 [4바이트 big-endian 길이] + [protobuf bytes] 형태로 인코딩합니다.
|
||||||
|
type protobufCodec struct{}
|
||||||
|
|
||||||
|
// Encode 는 Envelope 를 Protobuf Envelope 로 변환한 뒤, length-prefix 프레이밍으로 기록합니다.
|
||||||
|
// Encode encodes an Envelope as a length-prefixed protobuf message.
|
||||||
|
func (protobufCodec) Encode(w io.Writer, env *Envelope) error {
|
||||||
|
pbEnv, err := toProtoEnvelope(env)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
data, err := proto.Marshal(pbEnv)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("protobuf marshal envelope: %w", err)
|
||||||
|
}
|
||||||
|
if len(data) == 0 {
|
||||||
|
return fmt.Errorf("protobuf codec: empty marshaled envelope")
|
||||||
|
}
|
||||||
|
|
||||||
|
var lenBuf [4]byte
|
||||||
|
if len(data) > int(^uint32(0)) {
|
||||||
|
return fmt.Errorf("protobuf codec: envelope too large: %d bytes", len(data))
|
||||||
|
}
|
||||||
|
binary.BigEndian.PutUint32(lenBuf[:], uint32(len(data)))
|
||||||
|
|
||||||
|
if _, err := w.Write(lenBuf[:]); err != nil {
|
||||||
|
return fmt.Errorf("protobuf codec: write length prefix: %w", err)
|
||||||
|
}
|
||||||
|
if _, err := w.Write(data); err != nil {
|
||||||
|
return fmt.Errorf("protobuf codec: write payload: %w", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decode 는 length-prefix 프레임에서 Protobuf Envelope 를 읽어들여
|
||||||
|
// 내부 Envelope 구조체로 변환합니다.
|
||||||
|
// Decode reads a length-prefixed protobuf Envelope and converts it into the internal Envelope.
|
||||||
|
func (protobufCodec) Decode(r io.Reader, env *Envelope) error {
|
||||||
|
var lenBuf [4]byte
|
||||||
|
if _, err := io.ReadFull(r, lenBuf[:]); err != nil {
|
||||||
|
return fmt.Errorf("protobuf codec: read length prefix: %w", err)
|
||||||
|
}
|
||||||
|
n := binary.BigEndian.Uint32(lenBuf[:])
|
||||||
|
if n == 0 {
|
||||||
|
return fmt.Errorf("protobuf codec: zero-length envelope")
|
||||||
|
}
|
||||||
|
if n > maxProtoEnvelopeBytes {
|
||||||
|
return fmt.Errorf("protobuf codec: envelope too large: %d bytes (max %d)", n, maxProtoEnvelopeBytes)
|
||||||
|
}
|
||||||
|
|
||||||
|
buf := make([]byte, int(n))
|
||||||
|
if _, err := io.ReadFull(r, buf); err != nil {
|
||||||
|
return fmt.Errorf("protobuf codec: read payload: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var pbEnv protocolpb.Envelope
|
||||||
|
if err := proto.Unmarshal(buf, &pbEnv); err != nil {
|
||||||
|
return fmt.Errorf("protobuf codec: unmarshal envelope: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return fromProtoEnvelope(&pbEnv, env)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DefaultCodec 은 현재 런타임에서 사용하는 기본 WireCodec 입니다.
|
||||||
|
// 이제 Protobuf 기반 codec 을 기본으로 사용합니다.
|
||||||
|
var DefaultCodec WireCodec = protobufCodec{}
|
||||||
|
|
||||||
|
// toProtoEnvelope 는 내부 Envelope 구조체를 Protobuf Envelope 로 변환합니다.
|
||||||
|
// 현재 구현은 MessageTypeHTTP (HTTPRequest/HTTPResponse) 만 지원하며,
|
||||||
|
// 스트림 관련 타입은 이후 스트림 터널링 구현 단계에서 확장합니다.
|
||||||
|
func toProtoEnvelope(env *Envelope) (*protocolpb.Envelope, error) {
|
||||||
|
switch env.Type {
|
||||||
|
case MessageTypeHTTP:
|
||||||
|
if env.HTTPRequest != nil {
|
||||||
|
req := env.HTTPRequest
|
||||||
|
pbReq := &protocolpb.Request{
|
||||||
|
RequestId: req.RequestID,
|
||||||
|
ClientId: req.ClientID,
|
||||||
|
ServiceName: req.ServiceName,
|
||||||
|
Method: req.Method,
|
||||||
|
Url: req.URL,
|
||||||
|
Header: make(map[string]*protocolpb.HeaderValues, len(req.Header)),
|
||||||
|
Body: req.Body,
|
||||||
|
}
|
||||||
|
for k, vs := range req.Header {
|
||||||
|
hv := &protocolpb.HeaderValues{
|
||||||
|
Values: append([]string(nil), vs...),
|
||||||
|
}
|
||||||
|
pbReq.Header[k] = hv
|
||||||
|
}
|
||||||
|
return &protocolpb.Envelope{
|
||||||
|
Payload: &protocolpb.Envelope_HttpRequest{
|
||||||
|
HttpRequest: pbReq,
|
||||||
|
},
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
if env.HTTPResponse != nil {
|
||||||
|
resp := env.HTTPResponse
|
||||||
|
pbResp := &protocolpb.Response{
|
||||||
|
RequestId: resp.RequestID,
|
||||||
|
Status: int32(resp.Status),
|
||||||
|
Header: make(map[string]*protocolpb.HeaderValues, len(resp.Header)),
|
||||||
|
Body: resp.Body,
|
||||||
|
Error: resp.Error,
|
||||||
|
}
|
||||||
|
for k, vs := range resp.Header {
|
||||||
|
hv := &protocolpb.HeaderValues{
|
||||||
|
Values: append([]string(nil), vs...),
|
||||||
|
}
|
||||||
|
pbResp.Header[k] = hv
|
||||||
|
}
|
||||||
|
return &protocolpb.Envelope{
|
||||||
|
Payload: &protocolpb.Envelope_HttpResponse{
|
||||||
|
HttpResponse: pbResp,
|
||||||
|
},
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf("protobuf codec: http envelope has neither request nor response")
|
||||||
|
default:
|
||||||
|
// 스트림 관련 타입은 아직 DTLS 스트림 터널링 구현 이전 단계이므로 지원하지 않습니다.
|
||||||
|
// Stream-based message types are not yet supported by the protobuf codec.
|
||||||
|
return nil, fmt.Errorf("protobuf codec: unsupported envelope type %q", env.Type)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// fromProtoEnvelope 는 Protobuf Envelope 를 내부 Envelope 구조체로 변환합니다.
|
||||||
|
// 현재 구현은 HTTP 요청/응답만 지원합니다.
|
||||||
|
func fromProtoEnvelope(pbEnv *protocolpb.Envelope, env *Envelope) error {
|
||||||
|
switch payload := pbEnv.Payload.(type) {
|
||||||
|
case *protocolpb.Envelope_HttpRequest:
|
||||||
|
req := payload.HttpRequest
|
||||||
|
if req == nil {
|
||||||
|
return fmt.Errorf("protobuf codec: http_request payload is nil")
|
||||||
|
}
|
||||||
|
hdr := make(map[string][]string, len(req.Header))
|
||||||
|
for k, hv := range req.Header {
|
||||||
|
if hv == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
hdr[k] = append([]string(nil), hv.Values...)
|
||||||
|
}
|
||||||
|
env.Type = MessageTypeHTTP
|
||||||
|
env.HTTPRequest = &Request{
|
||||||
|
RequestID: req.RequestId,
|
||||||
|
ClientID: req.ClientId,
|
||||||
|
ServiceName: req.ServiceName,
|
||||||
|
Method: req.Method,
|
||||||
|
URL: req.Url,
|
||||||
|
Header: hdr,
|
||||||
|
Body: append([]byte(nil), req.Body...),
|
||||||
|
}
|
||||||
|
env.HTTPResponse = nil
|
||||||
|
return nil
|
||||||
|
|
||||||
|
case *protocolpb.Envelope_HttpResponse:
|
||||||
|
resp := payload.HttpResponse
|
||||||
|
if resp == nil {
|
||||||
|
return fmt.Errorf("protobuf codec: http_response payload is nil")
|
||||||
|
}
|
||||||
|
hdr := make(map[string][]string, len(resp.Header))
|
||||||
|
for k, hv := range resp.Header {
|
||||||
|
if hv == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
hdr[k] = append([]string(nil), hv.Values...)
|
||||||
|
}
|
||||||
|
env.Type = MessageTypeHTTP
|
||||||
|
env.HTTPResponse = &Response{
|
||||||
|
RequestID: resp.RequestId,
|
||||||
|
Status: int(resp.Status),
|
||||||
|
Header: hdr,
|
||||||
|
Body: append([]byte(nil), resp.Body...),
|
||||||
|
Error: resp.Error,
|
||||||
|
}
|
||||||
|
env.HTTPRequest = nil
|
||||||
|
return nil
|
||||||
|
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("protobuf codec: unsupported payload type %T", payload)
|
||||||
|
}
|
||||||
|
}
|
||||||
103
internal/protocol/hopgate_stream.proto
Normal file
103
internal/protocol/hopgate_stream.proto
Normal file
@@ -0,0 +1,103 @@
|
|||||||
|
syntax = "proto3";
|
||||||
|
|
||||||
|
package hopgate.protocol.v1;
|
||||||
|
|
||||||
|
option go_package = "github.com/dalbodeule/hop-gate/internal/protocol/pb;protocolpb";
|
||||||
|
|
||||||
|
// HeaderValues 는 HTTP 헤더의 다중 값 표현을 위한 래퍼입니다.
|
||||||
|
// HeaderValues wraps multiple header values for a single HTTP header key.
|
||||||
|
message HeaderValues {
|
||||||
|
repeated string values = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Request 는 DTLS 터널 위에서 교환되는 HTTP 요청을 표현합니다.
|
||||||
|
// This mirrors internal/protocol.Request.
|
||||||
|
message Request {
|
||||||
|
string request_id = 1;
|
||||||
|
string client_id = 2; // optional client identifier
|
||||||
|
string service_name = 3; // logical service name on the client side
|
||||||
|
|
||||||
|
string method = 4;
|
||||||
|
string url = 5;
|
||||||
|
|
||||||
|
// HTTP header: map of key -> multiple values.
|
||||||
|
map<string, HeaderValues> header = 6;
|
||||||
|
|
||||||
|
// Raw HTTP body bytes.
|
||||||
|
bytes body = 7;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Response 는 DTLS 터널 위에서 교환되는 HTTP 응답을 표현합니다.
|
||||||
|
// This mirrors internal/protocol.Response.
|
||||||
|
message Response {
|
||||||
|
string request_id = 1;
|
||||||
|
int32 status = 2;
|
||||||
|
|
||||||
|
// HTTP header.
|
||||||
|
map<string, HeaderValues> header = 3;
|
||||||
|
|
||||||
|
// Raw HTTP body bytes.
|
||||||
|
bytes body = 4;
|
||||||
|
|
||||||
|
// Optional error description when tunneling fails.
|
||||||
|
string error = 5;
|
||||||
|
}
|
||||||
|
|
||||||
|
// StreamOpen 은 새로운 스트림(HTTP 요청/응답, WebSocket 등)을 여는 메시지입니다.
|
||||||
|
// This represents opening a new stream (HTTP request/response, WebSocket, etc.).
|
||||||
|
message StreamOpen {
|
||||||
|
string id = 1; // StreamID (text form)
|
||||||
|
|
||||||
|
// Which logical service / local target to use on the client side.
|
||||||
|
string service_name = 2;
|
||||||
|
string target_addr = 3; // e.g. "127.0.0.1:8080"
|
||||||
|
|
||||||
|
// Initial HTTP-like headers (including Upgrade, etc.).
|
||||||
|
map<string, HeaderValues> header = 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
// StreamData 는 이미 열린 스트림에 대한 단방향 데이터 프레임입니다.
|
||||||
|
// This is a unidirectional data frame on an already-open stream.
|
||||||
|
message StreamData {
|
||||||
|
string id = 1; // StreamID
|
||||||
|
uint64 seq = 2; // per-stream sequence number starting from 0
|
||||||
|
bytes data = 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
// StreamAck 는 StreamData 에 대한 ACK/NACK 및 선택적 재전송 힌트를 전달합니다.
|
||||||
|
// This conveys ACK/NACK and optional retransmission hints for StreamData.
|
||||||
|
message StreamAck {
|
||||||
|
string id = 1;
|
||||||
|
|
||||||
|
// Last contiguously received sequence number (starting from 0).
|
||||||
|
uint64 ack_seq = 2;
|
||||||
|
|
||||||
|
// Additional missing sequence numbers beyond ack_seq (optional).
|
||||||
|
repeated uint64 lost_seqs = 3;
|
||||||
|
|
||||||
|
// Optional receive window size hint.
|
||||||
|
uint32 window_size = 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
// StreamClose 는 스트림 종료(정상/에러)를 알립니다.
|
||||||
|
// This indicates normal or error termination of a stream.
|
||||||
|
message StreamClose {
|
||||||
|
string id = 1;
|
||||||
|
string error = 2; // empty means normal close
|
||||||
|
}
|
||||||
|
|
||||||
|
// Envelope 는 DTLS 세션 위에서 교환되는 상위 레벨 메시지 컨테이너입니다.
|
||||||
|
// 하나의 Envelope 에는 HTTP 요청/응답 또는 스트림 관련 메시지 중 하나만 포함됩니다.
|
||||||
|
// Envelope is the top-level container exchanged over the DTLS session.
|
||||||
|
// Exactly one payload (http_request/http_response/stream_*) is set per message.
|
||||||
|
message Envelope {
|
||||||
|
oneof payload {
|
||||||
|
Request http_request = 1;
|
||||||
|
Response http_response = 2;
|
||||||
|
|
||||||
|
StreamOpen stream_open = 3;
|
||||||
|
StreamData stream_data = 4;
|
||||||
|
StreamClose stream_close = 5;
|
||||||
|
StreamAck stream_ack = 6;
|
||||||
|
}
|
||||||
|
}
|
||||||
799
internal/protocol/pb/hopgate_stream.pb.go
Normal file
799
internal/protocol/pb/hopgate_stream.pb.go
Normal file
@@ -0,0 +1,799 @@
|
|||||||
|
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||||
|
// versions:
|
||||||
|
// protoc-gen-go v1.36.10
|
||||||
|
// protoc v6.33.1
|
||||||
|
// source: internal/protocol/hopgate_stream.proto
|
||||||
|
|
||||||
|
package pb
|
||||||
|
|
||||||
|
import (
|
||||||
|
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
|
||||||
|
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
|
||||||
|
reflect "reflect"
|
||||||
|
sync "sync"
|
||||||
|
unsafe "unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// Verify that this generated code is sufficiently up-to-date.
|
||||||
|
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
|
||||||
|
// Verify that runtime/protoimpl is sufficiently up-to-date.
|
||||||
|
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
|
||||||
|
)
|
||||||
|
|
||||||
|
// HeaderValues 는 HTTP 헤더의 다중 값 표현을 위한 래퍼입니다.
|
||||||
|
// HeaderValues wraps multiple header values for a single HTTP header key.
|
||||||
|
type HeaderValues struct {
|
||||||
|
state protoimpl.MessageState `protogen:"open.v1"`
|
||||||
|
Values []string `protobuf:"bytes,1,rep,name=values,proto3" json:"values,omitempty"`
|
||||||
|
unknownFields protoimpl.UnknownFields
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *HeaderValues) Reset() {
|
||||||
|
*x = HeaderValues{}
|
||||||
|
mi := &file_internal_protocol_hopgate_stream_proto_msgTypes[0]
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *HeaderValues) String() string {
|
||||||
|
return protoimpl.X.MessageStringOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*HeaderValues) ProtoMessage() {}
|
||||||
|
|
||||||
|
func (x *HeaderValues) ProtoReflect() protoreflect.Message {
|
||||||
|
mi := &file_internal_protocol_hopgate_stream_proto_msgTypes[0]
|
||||||
|
if x != nil {
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
if ms.LoadMessageInfo() == nil {
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
return ms
|
||||||
|
}
|
||||||
|
return mi.MessageOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated: Use HeaderValues.ProtoReflect.Descriptor instead.
|
||||||
|
func (*HeaderValues) Descriptor() ([]byte, []int) {
|
||||||
|
return file_internal_protocol_hopgate_stream_proto_rawDescGZIP(), []int{0}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *HeaderValues) GetValues() []string {
|
||||||
|
if x != nil {
|
||||||
|
return x.Values
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Request 는 DTLS 터널 위에서 교환되는 HTTP 요청을 표현합니다.
|
||||||
|
// This mirrors internal/protocol.Request.
|
||||||
|
type Request struct {
|
||||||
|
state protoimpl.MessageState `protogen:"open.v1"`
|
||||||
|
RequestId string `protobuf:"bytes,1,opt,name=request_id,json=requestId,proto3" json:"request_id,omitempty"`
|
||||||
|
ClientId string `protobuf:"bytes,2,opt,name=client_id,json=clientId,proto3" json:"client_id,omitempty"` // optional client identifier
|
||||||
|
ServiceName string `protobuf:"bytes,3,opt,name=service_name,json=serviceName,proto3" json:"service_name,omitempty"` // logical service name on the client side
|
||||||
|
Method string `protobuf:"bytes,4,opt,name=method,proto3" json:"method,omitempty"`
|
||||||
|
Url string `protobuf:"bytes,5,opt,name=url,proto3" json:"url,omitempty"`
|
||||||
|
// HTTP header: map of key -> multiple values.
|
||||||
|
Header map[string]*HeaderValues `protobuf:"bytes,6,rep,name=header,proto3" json:"header,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"`
|
||||||
|
// Raw HTTP body bytes.
|
||||||
|
Body []byte `protobuf:"bytes,7,opt,name=body,proto3" json:"body,omitempty"`
|
||||||
|
unknownFields protoimpl.UnknownFields
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *Request) Reset() {
|
||||||
|
*x = Request{}
|
||||||
|
mi := &file_internal_protocol_hopgate_stream_proto_msgTypes[1]
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *Request) String() string {
|
||||||
|
return protoimpl.X.MessageStringOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*Request) ProtoMessage() {}
|
||||||
|
|
||||||
|
func (x *Request) ProtoReflect() protoreflect.Message {
|
||||||
|
mi := &file_internal_protocol_hopgate_stream_proto_msgTypes[1]
|
||||||
|
if x != nil {
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
if ms.LoadMessageInfo() == nil {
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
return ms
|
||||||
|
}
|
||||||
|
return mi.MessageOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated: Use Request.ProtoReflect.Descriptor instead.
|
||||||
|
func (*Request) Descriptor() ([]byte, []int) {
|
||||||
|
return file_internal_protocol_hopgate_stream_proto_rawDescGZIP(), []int{1}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *Request) GetRequestId() string {
|
||||||
|
if x != nil {
|
||||||
|
return x.RequestId
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *Request) GetClientId() string {
|
||||||
|
if x != nil {
|
||||||
|
return x.ClientId
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *Request) GetServiceName() string {
|
||||||
|
if x != nil {
|
||||||
|
return x.ServiceName
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *Request) GetMethod() string {
|
||||||
|
if x != nil {
|
||||||
|
return x.Method
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *Request) GetUrl() string {
|
||||||
|
if x != nil {
|
||||||
|
return x.Url
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *Request) GetHeader() map[string]*HeaderValues {
|
||||||
|
if x != nil {
|
||||||
|
return x.Header
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *Request) GetBody() []byte {
|
||||||
|
if x != nil {
|
||||||
|
return x.Body
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Response 는 DTLS 터널 위에서 교환되는 HTTP 응답을 표현합니다.
|
||||||
|
// This mirrors internal/protocol.Response.
|
||||||
|
type Response struct {
|
||||||
|
state protoimpl.MessageState `protogen:"open.v1"`
|
||||||
|
RequestId string `protobuf:"bytes,1,opt,name=request_id,json=requestId,proto3" json:"request_id,omitempty"`
|
||||||
|
Status int32 `protobuf:"varint,2,opt,name=status,proto3" json:"status,omitempty"`
|
||||||
|
// HTTP header.
|
||||||
|
Header map[string]*HeaderValues `protobuf:"bytes,3,rep,name=header,proto3" json:"header,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"`
|
||||||
|
// Raw HTTP body bytes.
|
||||||
|
Body []byte `protobuf:"bytes,4,opt,name=body,proto3" json:"body,omitempty"`
|
||||||
|
// Optional error description when tunneling fails.
|
||||||
|
Error string `protobuf:"bytes,5,opt,name=error,proto3" json:"error,omitempty"`
|
||||||
|
unknownFields protoimpl.UnknownFields
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *Response) Reset() {
|
||||||
|
*x = Response{}
|
||||||
|
mi := &file_internal_protocol_hopgate_stream_proto_msgTypes[2]
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *Response) String() string {
|
||||||
|
return protoimpl.X.MessageStringOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*Response) ProtoMessage() {}
|
||||||
|
|
||||||
|
func (x *Response) ProtoReflect() protoreflect.Message {
|
||||||
|
mi := &file_internal_protocol_hopgate_stream_proto_msgTypes[2]
|
||||||
|
if x != nil {
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
if ms.LoadMessageInfo() == nil {
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
return ms
|
||||||
|
}
|
||||||
|
return mi.MessageOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated: Use Response.ProtoReflect.Descriptor instead.
|
||||||
|
func (*Response) Descriptor() ([]byte, []int) {
|
||||||
|
return file_internal_protocol_hopgate_stream_proto_rawDescGZIP(), []int{2}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *Response) GetRequestId() string {
|
||||||
|
if x != nil {
|
||||||
|
return x.RequestId
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *Response) GetStatus() int32 {
|
||||||
|
if x != nil {
|
||||||
|
return x.Status
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *Response) GetHeader() map[string]*HeaderValues {
|
||||||
|
if x != nil {
|
||||||
|
return x.Header
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *Response) GetBody() []byte {
|
||||||
|
if x != nil {
|
||||||
|
return x.Body
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *Response) GetError() string {
|
||||||
|
if x != nil {
|
||||||
|
return x.Error
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// StreamOpen 은 새로운 스트림(HTTP 요청/응답, WebSocket 등)을 여는 메시지입니다.
|
||||||
|
// This represents opening a new stream (HTTP request/response, WebSocket, etc.).
|
||||||
|
type StreamOpen struct {
|
||||||
|
state protoimpl.MessageState `protogen:"open.v1"`
|
||||||
|
Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` // StreamID (text form)
|
||||||
|
// Which logical service / local target to use on the client side.
|
||||||
|
ServiceName string `protobuf:"bytes,2,opt,name=service_name,json=serviceName,proto3" json:"service_name,omitempty"`
|
||||||
|
TargetAddr string `protobuf:"bytes,3,opt,name=target_addr,json=targetAddr,proto3" json:"target_addr,omitempty"` // e.g. "127.0.0.1:8080"
|
||||||
|
// Initial HTTP-like headers (including Upgrade, etc.).
|
||||||
|
Header map[string]*HeaderValues `protobuf:"bytes,4,rep,name=header,proto3" json:"header,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"`
|
||||||
|
unknownFields protoimpl.UnknownFields
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *StreamOpen) Reset() {
|
||||||
|
*x = StreamOpen{}
|
||||||
|
mi := &file_internal_protocol_hopgate_stream_proto_msgTypes[3]
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *StreamOpen) String() string {
|
||||||
|
return protoimpl.X.MessageStringOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*StreamOpen) ProtoMessage() {}
|
||||||
|
|
||||||
|
func (x *StreamOpen) ProtoReflect() protoreflect.Message {
|
||||||
|
mi := &file_internal_protocol_hopgate_stream_proto_msgTypes[3]
|
||||||
|
if x != nil {
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
if ms.LoadMessageInfo() == nil {
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
return ms
|
||||||
|
}
|
||||||
|
return mi.MessageOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated: Use StreamOpen.ProtoReflect.Descriptor instead.
|
||||||
|
func (*StreamOpen) Descriptor() ([]byte, []int) {
|
||||||
|
return file_internal_protocol_hopgate_stream_proto_rawDescGZIP(), []int{3}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *StreamOpen) GetId() string {
|
||||||
|
if x != nil {
|
||||||
|
return x.Id
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *StreamOpen) GetServiceName() string {
|
||||||
|
if x != nil {
|
||||||
|
return x.ServiceName
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *StreamOpen) GetTargetAddr() string {
|
||||||
|
if x != nil {
|
||||||
|
return x.TargetAddr
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *StreamOpen) GetHeader() map[string]*HeaderValues {
|
||||||
|
if x != nil {
|
||||||
|
return x.Header
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// StreamData 는 이미 열린 스트림에 대한 단방향 데이터 프레임입니다.
|
||||||
|
// This is a unidirectional data frame on an already-open stream.
|
||||||
|
type StreamData struct {
|
||||||
|
state protoimpl.MessageState `protogen:"open.v1"`
|
||||||
|
Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` // StreamID
|
||||||
|
Seq uint64 `protobuf:"varint,2,opt,name=seq,proto3" json:"seq,omitempty"` // per-stream sequence number starting from 0
|
||||||
|
Data []byte `protobuf:"bytes,3,opt,name=data,proto3" json:"data,omitempty"`
|
||||||
|
unknownFields protoimpl.UnknownFields
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *StreamData) Reset() {
|
||||||
|
*x = StreamData{}
|
||||||
|
mi := &file_internal_protocol_hopgate_stream_proto_msgTypes[4]
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *StreamData) String() string {
|
||||||
|
return protoimpl.X.MessageStringOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*StreamData) ProtoMessage() {}
|
||||||
|
|
||||||
|
func (x *StreamData) ProtoReflect() protoreflect.Message {
|
||||||
|
mi := &file_internal_protocol_hopgate_stream_proto_msgTypes[4]
|
||||||
|
if x != nil {
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
if ms.LoadMessageInfo() == nil {
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
return ms
|
||||||
|
}
|
||||||
|
return mi.MessageOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated: Use StreamData.ProtoReflect.Descriptor instead.
|
||||||
|
func (*StreamData) Descriptor() ([]byte, []int) {
|
||||||
|
return file_internal_protocol_hopgate_stream_proto_rawDescGZIP(), []int{4}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *StreamData) GetId() string {
|
||||||
|
if x != nil {
|
||||||
|
return x.Id
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *StreamData) GetSeq() uint64 {
|
||||||
|
if x != nil {
|
||||||
|
return x.Seq
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *StreamData) GetData() []byte {
|
||||||
|
if x != nil {
|
||||||
|
return x.Data
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// StreamAck 는 StreamData 에 대한 ACK/NACK 및 선택적 재전송 힌트를 전달합니다.
|
||||||
|
// This conveys ACK/NACK and optional retransmission hints for StreamData.
|
||||||
|
type StreamAck struct {
|
||||||
|
state protoimpl.MessageState `protogen:"open.v1"`
|
||||||
|
Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"`
|
||||||
|
// Last contiguously received sequence number (starting from 0).
|
||||||
|
AckSeq uint64 `protobuf:"varint,2,opt,name=ack_seq,json=ackSeq,proto3" json:"ack_seq,omitempty"`
|
||||||
|
// Additional missing sequence numbers beyond ack_seq (optional).
|
||||||
|
LostSeqs []uint64 `protobuf:"varint,3,rep,packed,name=lost_seqs,json=lostSeqs,proto3" json:"lost_seqs,omitempty"`
|
||||||
|
// Optional receive window size hint.
|
||||||
|
WindowSize uint32 `protobuf:"varint,4,opt,name=window_size,json=windowSize,proto3" json:"window_size,omitempty"`
|
||||||
|
unknownFields protoimpl.UnknownFields
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *StreamAck) Reset() {
|
||||||
|
*x = StreamAck{}
|
||||||
|
mi := &file_internal_protocol_hopgate_stream_proto_msgTypes[5]
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *StreamAck) String() string {
|
||||||
|
return protoimpl.X.MessageStringOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*StreamAck) ProtoMessage() {}
|
||||||
|
|
||||||
|
func (x *StreamAck) ProtoReflect() protoreflect.Message {
|
||||||
|
mi := &file_internal_protocol_hopgate_stream_proto_msgTypes[5]
|
||||||
|
if x != nil {
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
if ms.LoadMessageInfo() == nil {
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
return ms
|
||||||
|
}
|
||||||
|
return mi.MessageOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated: Use StreamAck.ProtoReflect.Descriptor instead.
|
||||||
|
func (*StreamAck) Descriptor() ([]byte, []int) {
|
||||||
|
return file_internal_protocol_hopgate_stream_proto_rawDescGZIP(), []int{5}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *StreamAck) GetId() string {
|
||||||
|
if x != nil {
|
||||||
|
return x.Id
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *StreamAck) GetAckSeq() uint64 {
|
||||||
|
if x != nil {
|
||||||
|
return x.AckSeq
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *StreamAck) GetLostSeqs() []uint64 {
|
||||||
|
if x != nil {
|
||||||
|
return x.LostSeqs
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *StreamAck) GetWindowSize() uint32 {
|
||||||
|
if x != nil {
|
||||||
|
return x.WindowSize
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// StreamClose 는 스트림 종료(정상/에러)를 알립니다.
|
||||||
|
// This indicates normal or error termination of a stream.
|
||||||
|
type StreamClose struct {
|
||||||
|
state protoimpl.MessageState `protogen:"open.v1"`
|
||||||
|
Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"`
|
||||||
|
Error string `protobuf:"bytes,2,opt,name=error,proto3" json:"error,omitempty"` // empty means normal close
|
||||||
|
unknownFields protoimpl.UnknownFields
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *StreamClose) Reset() {
|
||||||
|
*x = StreamClose{}
|
||||||
|
mi := &file_internal_protocol_hopgate_stream_proto_msgTypes[6]
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *StreamClose) String() string {
|
||||||
|
return protoimpl.X.MessageStringOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*StreamClose) ProtoMessage() {}
|
||||||
|
|
||||||
|
func (x *StreamClose) ProtoReflect() protoreflect.Message {
|
||||||
|
mi := &file_internal_protocol_hopgate_stream_proto_msgTypes[6]
|
||||||
|
if x != nil {
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
if ms.LoadMessageInfo() == nil {
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
return ms
|
||||||
|
}
|
||||||
|
return mi.MessageOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated: Use StreamClose.ProtoReflect.Descriptor instead.
|
||||||
|
func (*StreamClose) Descriptor() ([]byte, []int) {
|
||||||
|
return file_internal_protocol_hopgate_stream_proto_rawDescGZIP(), []int{6}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *StreamClose) GetId() string {
|
||||||
|
if x != nil {
|
||||||
|
return x.Id
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *StreamClose) GetError() string {
|
||||||
|
if x != nil {
|
||||||
|
return x.Error
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// Envelope 는 DTLS 세션 위에서 교환되는 상위 레벨 메시지 컨테이너입니다.
|
||||||
|
// 하나의 Envelope 에는 HTTP 요청/응답 또는 스트림 관련 메시지 중 하나만 포함됩니다.
|
||||||
|
// Envelope is the top-level container exchanged over the DTLS session.
|
||||||
|
// Exactly one payload (http_request/http_response/stream_*) is set per message.
|
||||||
|
type Envelope struct {
|
||||||
|
state protoimpl.MessageState `protogen:"open.v1"`
|
||||||
|
// Types that are valid to be assigned to Payload:
|
||||||
|
//
|
||||||
|
// *Envelope_HttpRequest
|
||||||
|
// *Envelope_HttpResponse
|
||||||
|
// *Envelope_StreamOpen
|
||||||
|
// *Envelope_StreamData
|
||||||
|
// *Envelope_StreamClose
|
||||||
|
// *Envelope_StreamAck
|
||||||
|
Payload isEnvelope_Payload `protobuf_oneof:"payload"`
|
||||||
|
unknownFields protoimpl.UnknownFields
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *Envelope) Reset() {
|
||||||
|
*x = Envelope{}
|
||||||
|
mi := &file_internal_protocol_hopgate_stream_proto_msgTypes[7]
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *Envelope) String() string {
|
||||||
|
return protoimpl.X.MessageStringOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*Envelope) ProtoMessage() {}
|
||||||
|
|
||||||
|
func (x *Envelope) ProtoReflect() protoreflect.Message {
|
||||||
|
mi := &file_internal_protocol_hopgate_stream_proto_msgTypes[7]
|
||||||
|
if x != nil {
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
if ms.LoadMessageInfo() == nil {
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
return ms
|
||||||
|
}
|
||||||
|
return mi.MessageOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated: Use Envelope.ProtoReflect.Descriptor instead.
|
||||||
|
func (*Envelope) Descriptor() ([]byte, []int) {
|
||||||
|
return file_internal_protocol_hopgate_stream_proto_rawDescGZIP(), []int{7}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *Envelope) GetPayload() isEnvelope_Payload {
|
||||||
|
if x != nil {
|
||||||
|
return x.Payload
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *Envelope) GetHttpRequest() *Request {
|
||||||
|
if x != nil {
|
||||||
|
if x, ok := x.Payload.(*Envelope_HttpRequest); ok {
|
||||||
|
return x.HttpRequest
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *Envelope) GetHttpResponse() *Response {
|
||||||
|
if x != nil {
|
||||||
|
if x, ok := x.Payload.(*Envelope_HttpResponse); ok {
|
||||||
|
return x.HttpResponse
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *Envelope) GetStreamOpen() *StreamOpen {
|
||||||
|
if x != nil {
|
||||||
|
if x, ok := x.Payload.(*Envelope_StreamOpen); ok {
|
||||||
|
return x.StreamOpen
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *Envelope) GetStreamData() *StreamData {
|
||||||
|
if x != nil {
|
||||||
|
if x, ok := x.Payload.(*Envelope_StreamData); ok {
|
||||||
|
return x.StreamData
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *Envelope) GetStreamClose() *StreamClose {
|
||||||
|
if x != nil {
|
||||||
|
if x, ok := x.Payload.(*Envelope_StreamClose); ok {
|
||||||
|
return x.StreamClose
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *Envelope) GetStreamAck() *StreamAck {
|
||||||
|
if x != nil {
|
||||||
|
if x, ok := x.Payload.(*Envelope_StreamAck); ok {
|
||||||
|
return x.StreamAck
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type isEnvelope_Payload interface {
|
||||||
|
isEnvelope_Payload()
|
||||||
|
}
|
||||||
|
|
||||||
|
type Envelope_HttpRequest struct {
|
||||||
|
HttpRequest *Request `protobuf:"bytes,1,opt,name=http_request,json=httpRequest,proto3,oneof"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Envelope_HttpResponse struct {
|
||||||
|
HttpResponse *Response `protobuf:"bytes,2,opt,name=http_response,json=httpResponse,proto3,oneof"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Envelope_StreamOpen struct {
|
||||||
|
StreamOpen *StreamOpen `protobuf:"bytes,3,opt,name=stream_open,json=streamOpen,proto3,oneof"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Envelope_StreamData struct {
|
||||||
|
StreamData *StreamData `protobuf:"bytes,4,opt,name=stream_data,json=streamData,proto3,oneof"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Envelope_StreamClose struct {
|
||||||
|
StreamClose *StreamClose `protobuf:"bytes,5,opt,name=stream_close,json=streamClose,proto3,oneof"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Envelope_StreamAck struct {
|
||||||
|
StreamAck *StreamAck `protobuf:"bytes,6,opt,name=stream_ack,json=streamAck,proto3,oneof"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*Envelope_HttpRequest) isEnvelope_Payload() {}
|
||||||
|
|
||||||
|
func (*Envelope_HttpResponse) isEnvelope_Payload() {}
|
||||||
|
|
||||||
|
func (*Envelope_StreamOpen) isEnvelope_Payload() {}
|
||||||
|
|
||||||
|
func (*Envelope_StreamData) isEnvelope_Payload() {}
|
||||||
|
|
||||||
|
func (*Envelope_StreamClose) isEnvelope_Payload() {}
|
||||||
|
|
||||||
|
func (*Envelope_StreamAck) isEnvelope_Payload() {}
|
||||||
|
|
||||||
|
var File_internal_protocol_hopgate_stream_proto protoreflect.FileDescriptor
|
||||||
|
|
||||||
|
const file_internal_protocol_hopgate_stream_proto_rawDesc = "" +
|
||||||
|
"\n" +
|
||||||
|
"&internal/protocol/hopgate_stream.proto\x12\x13hopgate.protocol.v1\"&\n" +
|
||||||
|
"\fHeaderValues\x12\x16\n" +
|
||||||
|
"\x06values\x18\x01 \x03(\tR\x06values\"\xc6\x02\n" +
|
||||||
|
"\aRequest\x12\x1d\n" +
|
||||||
|
"\n" +
|
||||||
|
"request_id\x18\x01 \x01(\tR\trequestId\x12\x1b\n" +
|
||||||
|
"\tclient_id\x18\x02 \x01(\tR\bclientId\x12!\n" +
|
||||||
|
"\fservice_name\x18\x03 \x01(\tR\vserviceName\x12\x16\n" +
|
||||||
|
"\x06method\x18\x04 \x01(\tR\x06method\x12\x10\n" +
|
||||||
|
"\x03url\x18\x05 \x01(\tR\x03url\x12@\n" +
|
||||||
|
"\x06header\x18\x06 \x03(\v2(.hopgate.protocol.v1.Request.HeaderEntryR\x06header\x12\x12\n" +
|
||||||
|
"\x04body\x18\a \x01(\fR\x04body\x1a\\\n" +
|
||||||
|
"\vHeaderEntry\x12\x10\n" +
|
||||||
|
"\x03key\x18\x01 \x01(\tR\x03key\x127\n" +
|
||||||
|
"\x05value\x18\x02 \x01(\v2!.hopgate.protocol.v1.HeaderValuesR\x05value:\x028\x01\"\x8c\x02\n" +
|
||||||
|
"\bResponse\x12\x1d\n" +
|
||||||
|
"\n" +
|
||||||
|
"request_id\x18\x01 \x01(\tR\trequestId\x12\x16\n" +
|
||||||
|
"\x06status\x18\x02 \x01(\x05R\x06status\x12A\n" +
|
||||||
|
"\x06header\x18\x03 \x03(\v2).hopgate.protocol.v1.Response.HeaderEntryR\x06header\x12\x12\n" +
|
||||||
|
"\x04body\x18\x04 \x01(\fR\x04body\x12\x14\n" +
|
||||||
|
"\x05error\x18\x05 \x01(\tR\x05error\x1a\\\n" +
|
||||||
|
"\vHeaderEntry\x12\x10\n" +
|
||||||
|
"\x03key\x18\x01 \x01(\tR\x03key\x127\n" +
|
||||||
|
"\x05value\x18\x02 \x01(\v2!.hopgate.protocol.v1.HeaderValuesR\x05value:\x028\x01\"\x83\x02\n" +
|
||||||
|
"\n" +
|
||||||
|
"StreamOpen\x12\x0e\n" +
|
||||||
|
"\x02id\x18\x01 \x01(\tR\x02id\x12!\n" +
|
||||||
|
"\fservice_name\x18\x02 \x01(\tR\vserviceName\x12\x1f\n" +
|
||||||
|
"\vtarget_addr\x18\x03 \x01(\tR\n" +
|
||||||
|
"targetAddr\x12C\n" +
|
||||||
|
"\x06header\x18\x04 \x03(\v2+.hopgate.protocol.v1.StreamOpen.HeaderEntryR\x06header\x1a\\\n" +
|
||||||
|
"\vHeaderEntry\x12\x10\n" +
|
||||||
|
"\x03key\x18\x01 \x01(\tR\x03key\x127\n" +
|
||||||
|
"\x05value\x18\x02 \x01(\v2!.hopgate.protocol.v1.HeaderValuesR\x05value:\x028\x01\"B\n" +
|
||||||
|
"\n" +
|
||||||
|
"StreamData\x12\x0e\n" +
|
||||||
|
"\x02id\x18\x01 \x01(\tR\x02id\x12\x10\n" +
|
||||||
|
"\x03seq\x18\x02 \x01(\x04R\x03seq\x12\x12\n" +
|
||||||
|
"\x04data\x18\x03 \x01(\fR\x04data\"r\n" +
|
||||||
|
"\tStreamAck\x12\x0e\n" +
|
||||||
|
"\x02id\x18\x01 \x01(\tR\x02id\x12\x17\n" +
|
||||||
|
"\aack_seq\x18\x02 \x01(\x04R\x06ackSeq\x12\x1b\n" +
|
||||||
|
"\tlost_seqs\x18\x03 \x03(\x04R\blostSeqs\x12\x1f\n" +
|
||||||
|
"\vwindow_size\x18\x04 \x01(\rR\n" +
|
||||||
|
"windowSize\"3\n" +
|
||||||
|
"\vStreamClose\x12\x0e\n" +
|
||||||
|
"\x02id\x18\x01 \x01(\tR\x02id\x12\x14\n" +
|
||||||
|
"\x05error\x18\x02 \x01(\tR\x05error\"\xae\x03\n" +
|
||||||
|
"\bEnvelope\x12A\n" +
|
||||||
|
"\fhttp_request\x18\x01 \x01(\v2\x1c.hopgate.protocol.v1.RequestH\x00R\vhttpRequest\x12D\n" +
|
||||||
|
"\rhttp_response\x18\x02 \x01(\v2\x1d.hopgate.protocol.v1.ResponseH\x00R\fhttpResponse\x12B\n" +
|
||||||
|
"\vstream_open\x18\x03 \x01(\v2\x1f.hopgate.protocol.v1.StreamOpenH\x00R\n" +
|
||||||
|
"streamOpen\x12B\n" +
|
||||||
|
"\vstream_data\x18\x04 \x01(\v2\x1f.hopgate.protocol.v1.StreamDataH\x00R\n" +
|
||||||
|
"streamData\x12E\n" +
|
||||||
|
"\fstream_close\x18\x05 \x01(\v2 .hopgate.protocol.v1.StreamCloseH\x00R\vstreamClose\x12?\n" +
|
||||||
|
"\n" +
|
||||||
|
"stream_ack\x18\x06 \x01(\v2\x1e.hopgate.protocol.v1.StreamAckH\x00R\tstreamAckB\t\n" +
|
||||||
|
"\apayloadB@Z>github.com/dalbodeule/hop-gate/internal/protocol/pb;protocolpbb\x06proto3"
|
||||||
|
|
||||||
|
var (
|
||||||
|
file_internal_protocol_hopgate_stream_proto_rawDescOnce sync.Once
|
||||||
|
file_internal_protocol_hopgate_stream_proto_rawDescData []byte
|
||||||
|
)
|
||||||
|
|
||||||
|
func file_internal_protocol_hopgate_stream_proto_rawDescGZIP() []byte {
|
||||||
|
file_internal_protocol_hopgate_stream_proto_rawDescOnce.Do(func() {
|
||||||
|
file_internal_protocol_hopgate_stream_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_internal_protocol_hopgate_stream_proto_rawDesc), len(file_internal_protocol_hopgate_stream_proto_rawDesc)))
|
||||||
|
})
|
||||||
|
return file_internal_protocol_hopgate_stream_proto_rawDescData
|
||||||
|
}
|
||||||
|
|
||||||
|
var file_internal_protocol_hopgate_stream_proto_msgTypes = make([]protoimpl.MessageInfo, 11)
|
||||||
|
var file_internal_protocol_hopgate_stream_proto_goTypes = []any{
|
||||||
|
(*HeaderValues)(nil), // 0: hopgate.protocol.v1.HeaderValues
|
||||||
|
(*Request)(nil), // 1: hopgate.protocol.v1.Request
|
||||||
|
(*Response)(nil), // 2: hopgate.protocol.v1.Response
|
||||||
|
(*StreamOpen)(nil), // 3: hopgate.protocol.v1.StreamOpen
|
||||||
|
(*StreamData)(nil), // 4: hopgate.protocol.v1.StreamData
|
||||||
|
(*StreamAck)(nil), // 5: hopgate.protocol.v1.StreamAck
|
||||||
|
(*StreamClose)(nil), // 6: hopgate.protocol.v1.StreamClose
|
||||||
|
(*Envelope)(nil), // 7: hopgate.protocol.v1.Envelope
|
||||||
|
nil, // 8: hopgate.protocol.v1.Request.HeaderEntry
|
||||||
|
nil, // 9: hopgate.protocol.v1.Response.HeaderEntry
|
||||||
|
nil, // 10: hopgate.protocol.v1.StreamOpen.HeaderEntry
|
||||||
|
}
|
||||||
|
var file_internal_protocol_hopgate_stream_proto_depIdxs = []int32{
|
||||||
|
8, // 0: hopgate.protocol.v1.Request.header:type_name -> hopgate.protocol.v1.Request.HeaderEntry
|
||||||
|
9, // 1: hopgate.protocol.v1.Response.header:type_name -> hopgate.protocol.v1.Response.HeaderEntry
|
||||||
|
10, // 2: hopgate.protocol.v1.StreamOpen.header:type_name -> hopgate.protocol.v1.StreamOpen.HeaderEntry
|
||||||
|
1, // 3: hopgate.protocol.v1.Envelope.http_request:type_name -> hopgate.protocol.v1.Request
|
||||||
|
2, // 4: hopgate.protocol.v1.Envelope.http_response:type_name -> hopgate.protocol.v1.Response
|
||||||
|
3, // 5: hopgate.protocol.v1.Envelope.stream_open:type_name -> hopgate.protocol.v1.StreamOpen
|
||||||
|
4, // 6: hopgate.protocol.v1.Envelope.stream_data:type_name -> hopgate.protocol.v1.StreamData
|
||||||
|
6, // 7: hopgate.protocol.v1.Envelope.stream_close:type_name -> hopgate.protocol.v1.StreamClose
|
||||||
|
5, // 8: hopgate.protocol.v1.Envelope.stream_ack:type_name -> hopgate.protocol.v1.StreamAck
|
||||||
|
0, // 9: hopgate.protocol.v1.Request.HeaderEntry.value:type_name -> hopgate.protocol.v1.HeaderValues
|
||||||
|
0, // 10: hopgate.protocol.v1.Response.HeaderEntry.value:type_name -> hopgate.protocol.v1.HeaderValues
|
||||||
|
0, // 11: hopgate.protocol.v1.StreamOpen.HeaderEntry.value:type_name -> hopgate.protocol.v1.HeaderValues
|
||||||
|
12, // [12:12] is the sub-list for method output_type
|
||||||
|
12, // [12:12] is the sub-list for method input_type
|
||||||
|
12, // [12:12] is the sub-list for extension type_name
|
||||||
|
12, // [12:12] is the sub-list for extension extendee
|
||||||
|
0, // [0:12] is the sub-list for field type_name
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() { file_internal_protocol_hopgate_stream_proto_init() }
|
||||||
|
func file_internal_protocol_hopgate_stream_proto_init() {
|
||||||
|
if File_internal_protocol_hopgate_stream_proto != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
file_internal_protocol_hopgate_stream_proto_msgTypes[7].OneofWrappers = []any{
|
||||||
|
(*Envelope_HttpRequest)(nil),
|
||||||
|
(*Envelope_HttpResponse)(nil),
|
||||||
|
(*Envelope_StreamOpen)(nil),
|
||||||
|
(*Envelope_StreamData)(nil),
|
||||||
|
(*Envelope_StreamClose)(nil),
|
||||||
|
(*Envelope_StreamAck)(nil),
|
||||||
|
}
|
||||||
|
type x struct{}
|
||||||
|
out := protoimpl.TypeBuilder{
|
||||||
|
File: protoimpl.DescBuilder{
|
||||||
|
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
||||||
|
RawDescriptor: unsafe.Slice(unsafe.StringData(file_internal_protocol_hopgate_stream_proto_rawDesc), len(file_internal_protocol_hopgate_stream_proto_rawDesc)),
|
||||||
|
NumEnums: 0,
|
||||||
|
NumMessages: 11,
|
||||||
|
NumExtensions: 0,
|
||||||
|
NumServices: 0,
|
||||||
|
},
|
||||||
|
GoTypes: file_internal_protocol_hopgate_stream_proto_goTypes,
|
||||||
|
DependencyIndexes: file_internal_protocol_hopgate_stream_proto_depIdxs,
|
||||||
|
MessageInfos: file_internal_protocol_hopgate_stream_proto_msgTypes,
|
||||||
|
}.Build()
|
||||||
|
File_internal_protocol_hopgate_stream_proto = out.File
|
||||||
|
file_internal_protocol_hopgate_stream_proto_goTypes = nil
|
||||||
|
file_internal_protocol_hopgate_stream_proto_depIdxs = nil
|
||||||
|
}
|
||||||
@@ -1,10 +1,8 @@
|
|||||||
package proxy
|
package proxy
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bufio"
|
|
||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"net"
|
"net"
|
||||||
@@ -67,10 +65,10 @@ func (p *ClientProxy) StartLoop(ctx context.Context, sess dtls.Session) error {
|
|||||||
// "dtls: buffer too small" 오류가 날 수 있으므로, 여기서는 여유 있는 버퍼(64KiB)를 사용합니다. (ko)
|
// "dtls: buffer too small" 오류가 날 수 있으므로, 여기서는 여유 있는 버퍼(64KiB)를 사용합니다. (ko)
|
||||||
// NOTE: pion/dtls decrypts application data into the buffer provided by the caller.
|
// NOTE: pion/dtls decrypts application data into the buffer provided by the caller.
|
||||||
// Using only the default JSON decoder buffer (a few hundred bytes) can trigger
|
// Using only the default JSON decoder buffer (a few hundred bytes) can trigger
|
||||||
// "dtls: buffer too small" for large HTTP bodies/envelopes, so we wrap the
|
// "dtls: buffer too small" for large HTTP bodies/envelopes. The default
|
||||||
// session with a reasonably large bufio.Reader (64KiB). (en)
|
// JSON-based WireCodec internally wraps the DTLS session with a 64KiB
|
||||||
dec := json.NewDecoder(bufio.NewReaderSize(sess, 64*1024))
|
// bufio.Reader, matching this requirement. (en)
|
||||||
enc := json.NewEncoder(sess)
|
codec := protocol.DefaultCodec
|
||||||
|
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
@@ -83,7 +81,7 @@ func (p *ClientProxy) StartLoop(ctx context.Context, sess dtls.Session) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var env protocol.Envelope
|
var env protocol.Envelope
|
||||||
if err := dec.Decode(&env); err != nil {
|
if err := codec.Decode(sess, &env); err != nil {
|
||||||
if err == io.EOF {
|
if err == io.EOF {
|
||||||
log.Info("dtls session closed by server", nil)
|
log.Info("dtls session closed by server", nil)
|
||||||
return nil
|
return nil
|
||||||
@@ -135,7 +133,7 @@ func (p *ClientProxy) StartLoop(ctx context.Context, sess dtls.Session) error {
|
|||||||
HTTPResponse: &resp,
|
HTTPResponse: &resp,
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := enc.Encode(&respEnv); err != nil {
|
if err := codec.Encode(sess, &respEnv); err != nil {
|
||||||
logReq.Error("failed to encode http response envelope", logging.Fields{
|
logReq.Error("failed to encode http response envelope", logging.Fields{
|
||||||
"error": err.Error(),
|
"error": err.Error(),
|
||||||
})
|
})
|
||||||
|
|||||||
21
progress.md
21
progress.md
@@ -265,7 +265,7 @@ The following tasks describe concrete work items to be implemented on the `featu
|
|||||||
##### 3.3A.1 스트림 프레이밍 프로토콜 설계 (JSON 1단계)
|
##### 3.3A.1 스트림 프레이밍 프로토콜 설계 (JSON 1단계)
|
||||||
##### 3.3A.1 Stream framing protocol (JSON, phase 1)
|
##### 3.3A.1 Stream framing protocol (JSON, phase 1)
|
||||||
|
|
||||||
- [ ] 스트림 프레임 타입 정리 및 확장: [`internal/protocol/protocol.go`](internal/protocol/protocol.go:35)
|
- [x] 스트림 프레임 타입 정리 및 확장: [`internal/protocol/protocol.go`](internal/protocol/protocol.go:35)
|
||||||
- 이미 정의된 스트림 관련 타입을 1단계에서 적극 활용합니다.
|
- 이미 정의된 스트림 관련 타입을 1단계에서 적극 활용합니다.
|
||||||
Reuse the already defined stream-related types in phase 1:
|
Reuse the already defined stream-related types in phase 1:
|
||||||
- `MessageTypeStreamOpen`, `MessageTypeStreamData`, `MessageTypeStreamClose`
|
- `MessageTypeStreamOpen`, `MessageTypeStreamData`, `MessageTypeStreamClose`
|
||||||
@@ -281,7 +281,7 @@ The following tasks describe concrete work items to be implemented on the `featu
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
- [ ] 스트림 ACK / 재전송 제어 메시지 추가: [`internal/protocol/protocol.go`](internal/protocol/protocol.go:52)
|
- [x] 스트림 ACK / 재전송 제어 메시지 추가: [`internal/protocol/protocol.go`](internal/protocol/protocol.go:52)
|
||||||
- 선택적 재전송(Selective Retransmission)을 위해 `StreamAck` 메시지와 `MessageTypeStreamAck` 를 추가합니다.
|
- 선택적 재전송(Selective Retransmission)을 위해 `StreamAck` 메시지와 `MessageTypeStreamAck` 를 추가합니다.
|
||||||
Add `StreamAck` message and `MessageTypeStreamAck` for selective retransmission:
|
Add `StreamAck` message and `MessageTypeStreamAck` for selective retransmission:
|
||||||
```go
|
```go
|
||||||
@@ -310,7 +310,7 @@ The following tasks describe concrete work items to be implemented on the `featu
|
|||||||
##### 3.3A.2 애플리케이션 레벨 ARQ 설계 (Selective Retransmission)
|
##### 3.3A.2 애플리케이션 레벨 ARQ 설계 (Selective Retransmission)
|
||||||
##### 3.3A.2 Application-level ARQ (Selective Retransmission)
|
##### 3.3A.2 Application-level ARQ (Selective Retransmission)
|
||||||
|
|
||||||
- [ ] 수신 측 스트림 상태 관리 로직 설계
|
- [x] 수신 측 스트림 상태 관리 로직 설계
|
||||||
- 스트림별로 다음 상태를 유지합니다.
|
- 스트림별로 다음 상태를 유지합니다.
|
||||||
For each stream, maintain:
|
For each stream, maintain:
|
||||||
- `expectedSeq` (다음에 연속으로 기대하는 Seq, 초기값 0)
|
- `expectedSeq` (다음에 연속으로 기대하는 Seq, 초기값 0)
|
||||||
@@ -328,7 +328,7 @@ The following tasks describe concrete work items to be implemented on the `featu
|
|||||||
`expectedSeq` ~ `Seq-1` 구간 중 비어 있는 Seq 들을 `lostBuffer` 에 추가.
|
`expectedSeq` ~ `Seq-1` 구간 중 비어 있는 Seq 들을 `lostBuffer` 에 추가.
|
||||||
If `Seq > expectedSeq`, buffer as out-of-order and mark missing seqs in `lostBuffer`.
|
If `Seq > expectedSeq`, buffer as out-of-order and mark missing seqs in `lostBuffer`.
|
||||||
|
|
||||||
- [ ] 수신 측 StreamAck 전송 정책
|
- [x] 수신 측 StreamAck 전송 정책
|
||||||
- 주기적 타이머 또는 일정 수의 프레임 처리 후에 `StreamAck` 를 전송합니다.
|
- 주기적 타이머 또는 일정 수의 프레임 처리 후에 `StreamAck` 를 전송합니다.
|
||||||
Send `StreamAck` periodically or after processing N frames:
|
Send `StreamAck` periodically or after processing N frames:
|
||||||
- `AckSeq = expectedSeq - 1` (연속 수신 완료 지점)
|
- `AckSeq = expectedSeq - 1` (연속 수신 완료 지점)
|
||||||
@@ -336,7 +336,7 @@ The following tasks describe concrete work items to be implemented on the `featu
|
|||||||
- `LostSeqs` 는 윈도우 내 손실 시퀀스 중 상한 개수까지만 포함 (과도한 길이 방지).
|
- `LostSeqs` 는 윈도우 내 손실 시퀀스 중 상한 개수까지만 포함 (과도한 길이 방지).
|
||||||
`LostSeqs` should only include a bounded set of missing seqs within the receive window.
|
`LostSeqs` should only include a bounded set of missing seqs within the receive window.
|
||||||
|
|
||||||
- [ ] 송신 측 재전송 로직
|
- [x] 송신 측 재전송 로직
|
||||||
- 스트림별로 다음 상태를 유지합니다.
|
- 스트림별로 다음 상태를 유지합니다.
|
||||||
For each stream on the sender:
|
For each stream on the sender:
|
||||||
- `sendSeq` – 송신에 사용할 다음 Seq (0부터 시작)
|
- `sendSeq` – 송신에 사용할 다음 Seq (0부터 시작)
|
||||||
@@ -361,7 +361,7 @@ The following tasks describe concrete work items to be implemented on the `featu
|
|||||||
##### 3.3A.3 HTTP ↔ 스트림 매핑 (서버/클라이언트)
|
##### 3.3A.3 HTTP ↔ 스트림 매핑 (서버/클라이언트)
|
||||||
##### 3.3A.3 HTTP ↔ stream mapping (server/client)
|
##### 3.3A.3 HTTP ↔ stream mapping (server/client)
|
||||||
|
|
||||||
- [ ] 서버 → 클라이언트 요청 스트림: [`cmd/server/main.go`](cmd/server/main.go:200)
|
- [x] 서버 → 클라이언트 요청 스트림: [`cmd/server/main.go`](cmd/server/main.go:200)
|
||||||
- 현재 `ForwardHTTP` 는 단일 `HTTPRequest`/`HTTPResponse` 를 처리하는 구조입니다.
|
- 현재 `ForwardHTTP` 는 단일 `HTTPRequest`/`HTTPResponse` 를 처리하는 구조입니다.
|
||||||
Currently `ForwardHTTP` handles a single `HTTPRequest`/`HTTPResponse` pair.
|
Currently `ForwardHTTP` handles a single `HTTPRequest`/`HTTPResponse` pair.
|
||||||
- 스트림 모드에서는 다음과 같이 바꿉니다.
|
- 스트림 모드에서는 다음과 같이 바꿉니다.
|
||||||
@@ -385,7 +385,7 @@ The following tasks describe concrete work items to be implemented on the `featu
|
|||||||
- `StreamClose` 수신 시 응답 종료 및 스트림 자원 정리.
|
- `StreamClose` 수신 시 응답 종료 및 스트림 자원 정리.
|
||||||
On `StreamClose`, finish the response and clean up per-stream state.
|
On `StreamClose`, finish the response and clean up per-stream state.
|
||||||
|
|
||||||
- [ ] 클라이언트에서의 요청 처리 스트림: [`internal/proxy/client.go`](internal/proxy/client.go:200)
|
- [x] 클라이언트에서의 요청 처리 스트림: [`internal/proxy/client.go`](internal/proxy/client.go:200)
|
||||||
- 서버로부터 들어오는 `StreamOpen{ID, ...}` 을 수신하면,
|
- 서버로부터 들어오는 `StreamOpen{ID, ...}` 을 수신하면,
|
||||||
새로운 goroutine 을 띄워 해당 ID에 대한 로컬 HTTP 요청을 수행합니다.
|
새로운 goroutine 을 띄워 해당 ID에 대한 로컬 HTTP 요청을 수행합니다.
|
||||||
On receiving `StreamOpen{ID, ...}` from the server, spawn a goroutine to handle the local HTTP request for that stream ID.
|
On receiving `StreamOpen{ID, ...}` from the server, spawn a goroutine to handle the local HTTP request for that stream ID.
|
||||||
@@ -413,8 +413,11 @@ The following tasks describe concrete work items to be implemented on the `featu
|
|||||||
- 동일한 logical model (`StreamOpen` / `StreamData(seq)` / `StreamClose` / `StreamAck`)을 유지한 채,
|
- 동일한 logical model (`StreamOpen` / `StreamData(seq)` / `StreamClose` / `StreamAck`)을 유지한 채,
|
||||||
wire-format 만 Protobuf 또는 MsgPack 등의 length-prefix binary 프레이밍으로 교체할 수 있습니다.
|
wire-format 만 Protobuf 또는 MsgPack 등의 length-prefix binary 프레이밍으로 교체할 수 있습니다.
|
||||||
We can later keep the same logical model and swap the wire format for Protobuf or other length-prefix binary framing.
|
We can later keep the same logical model and swap the wire format for Protobuf or other length-prefix binary framing.
|
||||||
- 이 전환은 `internal/protocol` 내 직렬화 레이어를 얇은 abstraction 으로 감싸 구현할 수 있습니다.
|
- [x] 이 전환은 `internal/protocol` 내 직렬화 레이어를 얇은 abstraction 으로 감싸 구현할 수 있습니다.
|
||||||
This can be implemented by wrapping serialization in a thin abstraction layer inside [`internal/protocol`](internal/protocol/protocol.go:35).
|
- 현재는 [`internal/protocol/codec.go`](internal/protocol/codec.go:1) 에 `WireCodec` 인터페이스와 JSON 기반 `DefaultCodec` 을 도입하여,
|
||||||
|
추후 Protobuf/이진 포맷으로 교체할 때 호출자는 `protocol.DefaultCodec` 만 사용하도록 분리해 두었습니다.
|
||||||
|
- This has been prepared via [`internal/protocol/codec.go`](internal/protocol/codec.go:1), which introduces a `WireCodec` interface
|
||||||
|
and a JSON-based `DefaultCodec` so that future Protobuf/binary codecs can be swapped in behind the same API.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user