docs: refine gRPC guide section numbers and add CloseAndRecv & fileStore explanations
This commit is contained in:
+36
-23
@@ -40,7 +40,7 @@ gRPC는 HTTP/2를 기반으로 구축된 구글의 고성능 오픈소스 원격
|
||||
1. **서버 시간 및 날짜 조회 (`GetDate`)**: 기기가 접속 상태를 확인하며 동기화를 위해 서버의 현재 날짜와 시간 포맷 문자열을 반환받습니다.
|
||||
2. **센싱 데이터 업데이트 (`UpdateSensingData`)**: 센서 노드가 주기적으로 수집한 물리 데이터(온도, 습도) 및 기기 식별자(Device ID)를 전달하면, 서버는 데이터 정합성을 검증한 후 성공 여부를 반환합니다.
|
||||
3. **일회성 보안 패스워드 발급 (`GetRandomPass`)**: 기기가 임시 통신 세션 수립을 위해 난수 생성 시드와 보안 문자열 길이를 전달하면, 무작위 ASCII 임시 패스워드를 연산하여 응답받습니다.
|
||||
(참고: 여기서는 핵심 3종 Unary RPC를 우선 다루며, 대용량 파일 전송과 실시간 알림 기능은 §7에서 스트리밍 RPC 4종으로 이어서 확장합니다.)
|
||||
(참고: 여기서는 핵심 3종 Unary RPC를 우선 다루며, 대용량 파일 전송과 실시간 알림 기능은 §6 및 §7에서 스트리밍 RPC 4종으로 이어서 확장합니다.)
|
||||
|
||||
### 2.2 학습 목표 및 진행 방법
|
||||
이 유기적인 IoT 데이터 통신 모듈을 구축하는 실습을 통해 우리는 다음과 같은 gRPC의 핵심 개발 과정을 아주 쉽게 단계별로 마스터하게 됩니다:
|
||||
@@ -85,7 +85,7 @@ gRPC는 HTTP/2를 기반으로 구축된 구글의 고성능 오픈소스 원격
|
||||
|
||||
실습 디렉토리 내에 선언된 [protoapi.proto](../lib/grpc/basic/protoapi.proto) 명세서 코드는 앞서 기획한 `IoTService` 통신 구조를 수립하기 위해 다음과 같이 사양을 기재해 둡니다.
|
||||
|
||||
※ 이 코드는 기본 Unary RPC 3종만 발췌한 것이며, 전체 스펙(스트리밍 4종 포함)은 §7.1에서 이어집니다.
|
||||
※ 이 코드는 기본 Unary RPC 3종만 발췌한 것이며, 전체 스펙(스트리밍 4종 포함)은 §6.1에서 이어집니다.
|
||||
|
||||
```proto
|
||||
syntax = "proto3";
|
||||
@@ -207,7 +207,7 @@ protoc --go_out=. --go-grpc_out=. protoapi.proto
|
||||
protoapi.UnimplementedIoTServiceServer
|
||||
}
|
||||
```
|
||||
* **쉬운 설명**: 이 구조체는 '나는 IoTService가 약속한 기능들을 구현하는 서버입니다'라고 선언하는 역할을 합니다. **Go 언어의 gRPC 규칙상 이 구절(`Unimplemented...`)을 빼놓으면 서버가 아예 컴파일(빌드)되지 않고 에러가 발생하므로, '있으면 좋은 것'이 아니라 반드시 그대로 넣어주어야 하는 필수 구성 요소입니다.** (이렇게 넣어두면 부수적으로, 나중에 약속 장부에 새 기능이 추가되어도 기존 서버 코드가 빌드 오류 없이 구동되는 효과도 함께 얻습니다.)
|
||||
* **간단 설명**: 이 구조체는 '나는 IoTService가 약속한 기능들을 구현하는 서버입니다'라고 선언하는 역할을 합니다. **Go 언어의 gRPC 규칙상 이 구절(`Unimplemented...`)을 빼놓으면 서버가 아예 컴파일(빌드)되지 않고 에러가 발생하므로, '있으면 좋은 것'이 아니라 반드시 그대로 넣어주어야 하는 필수 구성 요소입니다.** (이렇게 넣어두면 부수적으로, 나중에 약속 장부에 새 기능이 추가되어도 기존 서버 코드가 빌드 오류 없이 구동되는 효과도 함께 얻습니다.)
|
||||
* **Q. 만약 이 줄(`protoapi.UnimplementedIoTServiceServer`)을 지우면 어떻게 되나요?**
|
||||
gRPC가 자동 생성한 인터페이스와의 호환성이 깨져 Go 컴파일러가 아래와 같은 에러를 내며 빌드를 거부합니다:
|
||||
```text
|
||||
@@ -223,7 +223,7 @@ protoc --go_out=. --go-grpc_out=. protoapi.proto
|
||||
return &protoapi.SensingResponse{Success: true, Message: "Sensing data updated successfully!"}, nil
|
||||
}
|
||||
```
|
||||
* **쉬운 설명**: 기기(클라이언트)가 온습도 데이터를 전송해 왔을 때, 서버 콘솔 화면에 이를 예쁘게 출력한 뒤 "성공적으로 업데이트되었습니다"라는 확인 영수증(`SensingResponse`)을 만들어 돌려주는 실제 서비스 동작 부위입니다.
|
||||
* **간단 설명**: 기기(클라이언트)가 온습도 데이터를 전송해 왔을 때, 서버 콘솔 화면에 이를 예쁘게 출력한 뒤 "성공적으로 업데이트되었습니다"라는 확인 영수증(`SensingResponse`)을 만들어 돌려주는 실제 서비스 동작 부위입니다.
|
||||
* **상세 설명**: 클라이언트 디바이스로부터 센싱 패킷을 수신하면, 기기 식별 및 온습도 측정 매개변수를 포맷팅하여 표준 출력에 표시한 후 정상 처리 완료 플래그를 담은 응답 구조체(`SensingResponse`)를 반환합니다.
|
||||
|
||||
> [!NOTE]
|
||||
@@ -240,7 +240,7 @@ protoc --go_out=. --go-grpc_out=. protoapi.proto
|
||||
listen, _ := net.Listen("tcp", port)
|
||||
server.Serve(listen)
|
||||
```
|
||||
* **쉬운 설명**: `:8080` 포트로 통하는 소켓(전화선)을 개통하고, 기기들의 전화(접속 및 호출)가 오기를 기다리며 대기 상태로 들어가는 서버 구동 시작점입니다.
|
||||
* **간단 설명**: `:8080` 포트로 통하는 소켓(전화선)을 개통하고, 기기들의 전화(접속 및 호출)가 오기를 기다리며 대기 상태로 들어가는 서버 구동 시작점입니다.
|
||||
* **상세 설명**: 지정된 포트(기본 포트 `:8080`)의 TCP 소켓 포트를 활성화하고, 클라이언트의 접속 및 RPC 서비스 호출에 대해 지속적으로 대기하는 리스너 구동의 진입점입니다.
|
||||
|
||||
### 5.2 gRPC 클라이언트 구현 분석 ([client.go](../lib/grpc/basic/client.go))
|
||||
@@ -250,14 +250,14 @@ protoc --go_out=. --go-grpc_out=. protoapi.proto
|
||||
conn, _ := grpc.NewClient(addr, grpc.WithTransportCredentials(insecure.NewCredentials()))
|
||||
client := protoapi.NewIoTServiceClient(conn)
|
||||
```
|
||||
* **쉬운 설명**: 서버 주소로 전화를 거는 통신선(TCP 채널)을 안전 보안(TLS) 없이 개설한 뒤, 이 선을 통해 gRPC 약속 장부(`IoTService`)대로 서버에 원격 호출을 요청할 수 있는 전용 전화기(클라이언트 인스턴스)를 획득하는 과정입니다.
|
||||
* **간단 설명**: 서버 주소로 전화를 거는 통신선(TCP 채널)을 안전 보안(TLS) 없이 개설한 뒤, 이 선을 통해 gRPC 약속 장부(`IoTService`)대로 서버에 원격 호출을 요청할 수 있는 전용 전화기(클라이언트 인스턴스)를 획득하는 과정입니다.
|
||||
* **상세 설명**: 서버와의 TCP 채널(`conn`)을 평문 전송(insecure) 기반으로 바인딩한 뒤, 해당 채널을 통해 원격 서비스를 호출할 수 있는 전송용 클라이언트 인스턴스를 확보합니다.
|
||||
* **패킷 구성 및 RPC 호출 실행 (`AskUpdateSensingData`)**:
|
||||
```go
|
||||
request := &protoapi.SensingData{DeviceId: deviceId, Temperature: temp, Humidity: humid}
|
||||
return m.UpdateSensingData(ctx, request)
|
||||
```
|
||||
* **쉬운 설명**: 온습도 데이터 상자(`SensingData`)를 접어서 기기 번호와 센서값을 가지런히 담은 뒤, 전용 전화기(클라이언트 인터페이스)를 통해 서버의 `UpdateSensingData` 기능을 직접 원격 실행(호출)하는 부분입니다.
|
||||
* **간단 설명**: 온습도 데이터 상자(`SensingData`)를 접어서 기기 번호와 센서값을 가지런히 담은 뒤, 전용 전화기(클라이언트 인터페이스)를 통해 서버의 `UpdateSensingData` 기능을 직접 원격 실행(호출)하는 부분입니다.
|
||||
* **상세 설명**: 센서 측정값을 메시지 스펙 규격에 맞추어 `SensingData` 구조체 인스턴스로 바인딩한 후, 기설정된 클라이언트 인터페이스를 경유하여 서버의 `UpdateSensingData` 엔드포인트를 호출합니다.
|
||||
|
||||
### 5.3 통신 세션 동작 시퀀스 및 흐름
|
||||
@@ -315,10 +315,24 @@ message DownloadRequest {
|
||||
string FileName = 1;
|
||||
}
|
||||
```
|
||||
* **설계 포인트**: `stream` 키워드가 들어간 위치에 주목합니다. `UploadFile`은 입력에 `stream`이 붙어 클라이언트 스트리밍을, `DownloadFile`은 반환(returns)에 `stream`이 붙어 서버 스트리밍 채널을 개설합니다.
|
||||
* **설계 포인트**: `stream` 키워드가 들어간 위치에 주목합니다.
|
||||
* **`stream` 키워드의 역할**: gRPC에서 `stream` 키워드는 단발성 요청/응답(Unary RPC) 방식과 달리, **하나의 HTTP/2 커넥션을 유지한 채 데이터를 연속적인 흐름(Stream)으로 쪼개서 전송하겠다**고 선언하는 지시어입니다. 이 키워드가 지정되면 컴파일러(`protoc`)는 데이터를 연속으로 송수신할 수 있는 스트림 파이프라인 형태의 Go 인터페이스와 스터브 코드를 생성합니다.
|
||||
* **클라이언트 스트리밍 (`UploadFile`)**: 호출 매개변수 정의 앞부분에 `stream`(`stream FileChunk`)이 붙습니다. 클라이언트가 데이터를 여러 번에 걸쳐 청크로 송신하고, 서버는 최종 수신 완료 시점에 단 한 번 응답(`returns (UploadStatus)`)을 반환합니다.
|
||||
* **서버 스트리밍 (`DownloadFile` 및 `SubscribeAlerts`)**: 반환형(`returns`) 정의의 괄호 내부에 `stream`(`returns (stream FileChunk)`)이 붙습니다. 클라이언트의 단일 호출 요청에 대해, 서버가 데이터를 여러 조각으로 쪼개어 연속적으로 클라이언트에게 푸시 전송합니다.
|
||||
* **🚨 `stream` 키워드 없이 대용량 데이터를 전송할 때의 한계와 위험성**:
|
||||
만약 대용량 파일나 수기가바이트(GB)에 달하는 대용량 데이터를 `stream` 키워드 없이 일반 Unary RPC(단발성 요청/응답)로 전송하려고 시도하면 다음과 같은 심각한 기술적 문제가 발생합니다:
|
||||
1. **메모리 고갈 (OOM - Out Of Memory)**: 전송할 데이터 전체가 직렬화되기 전 단일 바이트 슬라이스(`[]byte`) 형태로 클라이언트와 서버 메모리에 한 번에 적재되어야 합니다. 이는 메모리 리소스가 극도로 제한된 IoT 임베디드 단말기나 동시 요청이 몰리는 서버 환경에서 즉각적인 OOM 에러 및 프로세스 강제 종료를 유발합니다.
|
||||
2. **gRPC 기본 메시지 수신 한도 초과**: gRPC 엔진은 악의적인 디도스(DDoS) 공격 방지와 메모리 보호를 위해 **단일 RPC 호출당 최대 메시지 수신 크기를 기본 4MB**로 제한하고 있습니다. 따라서 4MB를 넘는 파일 데이터를 Unary로 전송할 경우 즉각 `ResourceExhausted` 에러가 발생하며 통신이 차단됩니다. (설정으로 한도를 늘릴 수 있으나 메모리 병목 우려로 권장되지 않습니다.)
|
||||
3. **네트워크 유실 시 재전송 오버헤드**: 단 한 번의 네트워크 패킷 전송 오류(Glitch)가 발생해도 전체 대용량 데이터를 처음부터 완전히 다시 보내야 하므로 네트워크 비용 낭비가 심화됩니다. 스트리밍을 사용하면 청크 단위로 분할하여 안정적으로 주고받을 수 있습니다.
|
||||
|
||||
### 6.2 서버 저장 및 송수신 구현 ([server.go](../lib/grpc/basic/server.go))
|
||||
인메모리 파일 저장소(`fileStore`)를 구현하고 목록 조회(`ListFiles`) 및 서버 다운로드 스트리밍(`DownloadFile`) 핸들러를 정의합니다:
|
||||
클라이언트가 스트리밍으로 업로드한 파일의 메타데이터를 서버에서 관리하기 위해 인메모리 파일 저장소인 `fileStore`를 정의합니다. `fileStore`는 파일 이름을 키(Key)로 하고 파일 메타데이터 및 바이트 데이터를 포함하는 `UploadedFile` 구조체 포인터를 값(Value)으로 갖는 맵(`map[string]*UploadedFile`) 구조체입니다.
|
||||
|
||||
이 저장소를 활용하여 다음 3가지 핸들러를 구현합니다:
|
||||
1. **파일 업로드 (`UploadFile` - Client Streaming)**: 클라이언트가 스트리밍을 통해 분할 전송하는 파일 데이터 조각(`FileChunk`)들을 수신해 병합한 후 `fileStore`에 등록합니다.
|
||||
2. **파일 목록 조회 (`ListFiles` - Unary RPC)**: `fileStore`에 보관된 모든 파일들의 메타데이터(파일명, 크기, 타임스탬프) 목록을 빌드하여 일괄 반환합니다.
|
||||
3. **파일 다운로드 (`DownloadFile` - Server Streaming)**: 요청된 파일 데이터를 `fileStore`에서 탐색한 후, 1KB 단위의 청크 조각으로 나누어 클라이언트에게 순차적으로 스트리밍 전송합니다.
|
||||
|
||||
|
||||
```go
|
||||
// 1. 인메모리 파일 보관소
|
||||
@@ -418,7 +432,7 @@ func (IoTServer) DownloadFile(r *protoapi.DownloadRequest, stream protoapi.IoTSe
|
||||
return nil
|
||||
}
|
||||
```
|
||||
* **쉬운 설명**:
|
||||
* **간단 설명**:
|
||||
* **파일 업로드**: 클라이언트가 쪼개서 던지는 파일 조각 상자(`stream.Recv()`)들을 루프를 돌며 계속 수집하여 하나의 임시 보관 버퍼(`buffer`)에 합칩니다. 마지막 조각 전송 완료(`io.EOF`) 신호가 오면, 모인 바이트들을 파일 이름과 함께 서버의 보관함(`fileStore`)에 안전하게 저장하고 영수증을 클라이언트에게 발행합니다.
|
||||
* **목록 조회**: 서버의 파일 보관함(`fileStore`)을 열고 그 안에 든 모든 파일의 메타데이터(이름, 크기, 업로드 시각)를 리스트로 포장해 한번에 리턴해 줍니다.
|
||||
* **파일 다운로드**: 보관함에서 요청받은 파일을 찾은 뒤, 파일 내용 전체를 1KB 크기의 패킷 조각들로 잘라 통로를 타고 차례대로 연속 전송(`stream.Send()`)해 줍니다.
|
||||
@@ -485,22 +499,23 @@ func AskDownloadFile(ctx context.Context, m protoapi.IoTServiceClient, fileName
|
||||
return buffer, nil
|
||||
}
|
||||
```
|
||||
* **쉬운 설명**:
|
||||
* **간단 설명**:
|
||||
* **파일 업로드**: 보낼 파일을 1KB 조각 크기로 나누어 준비한 뒤, gRPC 전용 파이프 스트림 통로에 차례대로 흘려보냅니다(`stream.Send()`). 모든 조각을 던진 후 채널을 끊고 영수증(`UploadStatus`)을 받습니다.
|
||||
* **목록 조회**: 서버에게 "보관 중인 파일 이름 목록을 달라"고 요구하여 화면에 출력합니다.
|
||||
* **파일 다운로드**: 다운로드 통로를 열어 서버가 던져주는 조각들을 계속 수령(`stream.Recv()`)하여 버퍼에 차곡차곡 합칩니다. 서버가 보내기를 끝마치면(`io.EOF`) 조립을 중단하고 최종 완성된 온전한 바이트 파일을 최종 사용처에 반환합니다.
|
||||
* **상세 설명**:
|
||||
* **파일 업로드**: `UploadFile` 채널을 기동하여 클라이언트 사이드 스트림 핸들을 획득합니다. 슬라이스 윈도우 방식으로 데이터를 순차 분할 송출하고, `CloseAndRecv` 메서드를 최종 호출해 스트림 종결 프레임을 송신한 뒤 서버의 단발성 최종 회신 상태를 획득합니다.
|
||||
* **목록 조회**: 빈 메시지(`EmptyRequest`)를 동봉해 Unary RPC 채널을 트리거하고 메타데이터 배열 결과를 동기 획득합니다.
|
||||
* **`CloseAndRecv()`의 역할**: 클라이언트에서 송신 스트림을 닫는(Half-close) 동시에, 서버가 전송 완료 후 최종 반환하는 응답 영수증(`UploadStatus`)을 수신할 때까지 블로킹 대기(Blocking wait)하여 최종 응답과 에러 객체를 받아오는 복합 기능을 수행합니다.
|
||||
* **목록 조회**: 빈 메시지(`EmptyRequest`)를 동봉해 Unary RPC 채널을 트리거하고 메타데이터 배열 결과를 동기적으로 (Synchronously) 획득합니다.
|
||||
* **파일 다운로드**: 서버 스트리밍 엔드포인트 기동 후, `stream.Recv()` 블로킹 수신 루프에 진입합니다. 채널 해제 지점(`io.EOF`)에 도달할 때까지 메모리 버퍼 슬라이스에 청크 바이트 배열을 병합 누적하여 재조립(Reassembly)을 마친 후 반환합니다.
|
||||
|
||||
### 6.4 실시간 알림을 위한 Pub/Sub (발행/구독) 브로드캐스팅 구현
|
||||
## 7. 실시간 알림을 위한 Pub/Sub (발행/구독) 브로드캐스팅 구현
|
||||
|
||||
쉽게 말해 Pub/Sub은 '신문 구독'과 같습니다. 구독자(클라이언트)가 한 번 신청해 두면, 발행자(서버)는 새로운 소식(경보)이 생길 때마다 모든 구독자에게 알아서 배달해 줍니다. 클라이언트가 매번 '무슨 일 없어요?'라고 다시 물어볼 필요가 없다는 것이 핵심입니다.
|
||||
|
||||
스마트 가전이나 센서 등 실시간 경보 통지가 필요한 AIoT 도메인에서는, 서버가 상시 대기하는 여러 디바이스(클라이언트)들에게 비동기로 이벤트를 밀어 넣어주는 **발행/구독(Publish/Subscribe)** 연동 구조가 필수적입니다. gRPC의 **서버 스트리밍(Server Streaming)** 채널을 응용하면, 다수의 클라이언트가 스트림 통로를 상시 유지한 채 대기하고, 서버가 특정 이벤트 발생 시 채널 리스트를 순회하며 실시간 이벤트를 **브로드캐스팅(Broadcasting)**하는 Pub/Sub 인프라를 단순하고 가볍게 완성할 수 있습니다.
|
||||
|
||||
#### 1. 스키마 설계 (`protoapi.proto`)
|
||||
### 7.1 스키마 설계 (protoapi.proto)
|
||||
구독 신청을 위한 파라미터(`AlertSubscription`)와 서버가 밀어 넣어줄 이벤트 규격(`AlertMessage`)을 IDL에 선언합니다:
|
||||
```proto
|
||||
message AlertSubscription {
|
||||
@@ -516,7 +531,7 @@ message AlertMessage {
|
||||
}
|
||||
```
|
||||
|
||||
#### 2. 서버 사이드 구독자 관리 및 발행 구현 ([server.go](../lib/grpc/basic/server.go))
|
||||
### 7.2 서버 사이드 구독자 관리 및 발행 구현 ([server.go](../lib/grpc/basic/server.go))
|
||||
서버는 구독을 신청한 클라이언트들에게 메시지를 안전하게 분배하기 위해 스레드 세이프 맵과 고루틴 채널(`chan`) 구조를 구성합니다:
|
||||
|
||||
```go
|
||||
@@ -579,14 +594,14 @@ func (IoTServer) SubscribeAlerts(r *protoapi.AlertSubscription, stream protoapi.
|
||||
}
|
||||
}
|
||||
```
|
||||
* **쉬운 설명**:
|
||||
* **간단 설명**:
|
||||
* **구독 신청**: 클라이언트가 전화를 걸면 서버는 그 선을 닫지 않고 메모장(`subscribers`)에 해당 전화번호와 연결된 통로(Go 채널)를 적어둡니다. 그리고 그 선을 계속 붙잡고 대기(`stream.Send`) 상태를 유지합니다.
|
||||
* **경보 발행**: 센서값 수신 핸들러(`UpdateSensingData`)에서 임계치(40도)를 초과하는 위험 열기가 감지되면, 메모장에 적힌 모든 연결된 통로에 경보 엽서(`AlertMessage`)를 휙 던져(Broadcast) 줍니다.
|
||||
* **상세 설명**:
|
||||
* **구독 신청**: `SubscribeAlerts` 엔드포인트는 호출과 동시에 전용 Go 비동기 버퍼 채널을 생성하고 전역 가입 맵에 등록합니다. `stream.Context().Done()` 채널 수신이나 스트림 유실 이벤트가 포착되기 전까지 루프 대기 상태를 안전하게 고정합니다.
|
||||
* **경보 발행**: 동시성 경쟁 방지 락(`subMu.Lock()`) 임계 구역 내에서 연결된 모든 채널에 데이터를 `select-default` 논블로킹 패턴으로 분배 기입하여, 특정 클라이언트의 수신 병목이 서버 전체 성능에 미치는 파급 효과를 예방합니다.
|
||||
|
||||
#### 3. 클라이언트 비동기 청취 구현 ([client.go](../lib/grpc/basic/client.go))
|
||||
### 7.3 클라이언트 비동기 청취 구현 ([client.go](../lib/grpc/basic/client.go))
|
||||
클라이언트는 메인 흐름을 방해하지 않고 알림을 백그라운드에서 실시간으로 대기 청취할 수 있도록 별도의 독자적인 비동기 고루틴 구조로 가동합니다.
|
||||
|
||||
```go
|
||||
@@ -614,22 +629,20 @@ func AskSubscribeAlerts(ctx context.Context, m protoapi.IoTServiceClient, client
|
||||
}
|
||||
}
|
||||
```
|
||||
* **쉬운 설명**: 클라이언트는 메인 로직이 다른 볼일(파일 업로드/다운로드 등)을 보러 간 동안, 옆방에서 전화를 붙잡고 계속 귀를 기울이는 전담 직원(비동기 고루틴)을 기동시킵니다. 서버에서 "벨(알림)"이 울릴 때마다 그 내용을 즉시 가로채 화면에 실시간 경보 창을 출력해 줍니다.
|
||||
* **간단 설명**: 클라이언트는 메인 로직이 다른 볼일(파일 업로드/다운로드 등)을 보러 간 동안, 옆방에서 전화를 붙잡고 계속 귀를 기울이는 전담 직원(비동기 고루틴)을 기동시킵니다. 서버에서 "벨(알림)"이 울릴 때마다 그 내용을 즉시 가로채 화면에 실시간 경보 창을 출력해 줍니다.
|
||||
* **상세 설명**: 메인 쓰레드의 블로킹을 방지하기 위해 Go의 경량 쓰레드 고루틴(`go AskSubscribeAlerts`)으로 리스너 루프를 위임 기동합니다. gRPC 스트림 클라이언트의 `stream.Recv()` 메서드는 서버로부터 메시지가 전달될 때까지 스레드 리소스를 낭비하지 않는 대기 상태로 머물며, 데이터 수령 시 콘솔 스트림에 이를 비동기 매핑 출력합니다.
|
||||
|
||||
---
|
||||
|
||||
## 7. 트러블슈팅 (Troubleshooting)
|
||||
## 8. 트러블슈팅 (Troubleshooting)
|
||||
|
||||
실습 구동 과정에서 마주할 수 있는 전형적인 에러 현상과 대처 방안입니다.
|
||||
|
||||
### 7.1 `bind: address already in use` (네트워크 소켓 포트 충돌)
|
||||
### 8.1 bind: address already in use (네트워크 소켓 포트 충돌)
|
||||
* **발생 원인**: gRPC 서버 기동 시 설정한 통신 포트 `:8080`이 이미 다른 네트워크 프로세스나 이전 실습 서버의 비정상 종료 등으로 인해 점유되어 바인딩에 실패한 상태입니다.
|
||||
* **조치 방법**:
|
||||
- `lib/main.go`의 `port` 변수 값을 다른 유휴 포트(예: `:9090`)로 변경한 뒤 다시 실행하십시오. `ServerRun(addr)` 함수가 이 값을 메인 진입점으로부터 인자로 전달받아 TCP 리스너를 생성하므로, `main.go` 한 곳만 수정하면 서버와 클라이언트의 통신 포트가 동시에 성공적으로 변경됩니다.
|
||||
|
||||
---
|
||||
|
||||
## 8. 참고 자료
|
||||
## 9. 참고 자료
|
||||
|
||||
* [gRPC와 REST의 차이점 (AWS)](https://aws.amazon.com/ko/compare/the-difference-between-grpc-and-rest/): 두 방식의 특징과 언제 어떤 기술을 선택해야 하는지 친절하게 정리된 공식 블로그 자료입니다.
|
||||
|
||||
@@ -3,41 +3,15 @@ module grpccanary
|
||||
go 1.25.4
|
||||
|
||||
require (
|
||||
github.com/gin-gonic/gin v1.11.0
|
||||
github.com/quic-go/quic-go v0.60.0
|
||||
google.golang.org/grpc v1.76.0
|
||||
google.golang.org/protobuf v1.36.10
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/bytedance/sonic v1.14.0 // indirect
|
||||
github.com/bytedance/sonic/loader v0.3.0 // indirect
|
||||
github.com/cloudwego/base64x v0.1.6 // indirect
|
||||
github.com/gabriel-vasile/mimetype v1.4.8 // indirect
|
||||
github.com/gin-contrib/sse v1.1.0 // indirect
|
||||
github.com/go-playground/locales v0.14.1 // indirect
|
||||
github.com/go-playground/universal-translator v0.18.1 // indirect
|
||||
github.com/go-playground/validator/v10 v10.27.0 // indirect
|
||||
github.com/goccy/go-json v0.10.2 // indirect
|
||||
github.com/goccy/go-yaml v1.18.0 // indirect
|
||||
github.com/json-iterator/go v1.1.12 // indirect
|
||||
github.com/klauspost/cpuid/v2 v2.3.0 // indirect
|
||||
github.com/leodido/go-urn v1.4.0 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 // indirect
|
||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.2.4 // indirect
|
||||
github.com/quic-go/qpack v0.5.1 // indirect
|
||||
github.com/quic-go/quic-go v0.54.0 // indirect
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
||||
github.com/ugorji/go/codec v1.3.0 // indirect
|
||||
go.uber.org/mock v0.5.0 // indirect
|
||||
golang.org/x/arch v0.20.0 // indirect
|
||||
golang.org/x/crypto v0.40.0 // indirect
|
||||
golang.org/x/mod v0.25.0 // indirect
|
||||
golang.org/x/net v0.42.0 // indirect
|
||||
golang.org/x/sync v0.16.0 // indirect
|
||||
golang.org/x/sys v0.35.0 // indirect
|
||||
golang.org/x/text v0.27.0 // indirect
|
||||
golang.org/x/tools v0.34.0 // indirect
|
||||
golang.org/x/crypto v0.51.0 // indirect
|
||||
golang.org/x/net v0.55.0 // indirect
|
||||
golang.org/x/sys v0.45.0 // indirect
|
||||
golang.org/x/text v0.37.0 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250804133106-a7a43d27e69b // indirect
|
||||
)
|
||||
|
||||
@@ -1,74 +1,23 @@
|
||||
github.com/bytedance/sonic v1.14.0 h1:/OfKt8HFw0kh2rj8N0F6C/qPGRESq0BbaNZgcNXXzQQ=
|
||||
github.com/bytedance/sonic v1.14.0/go.mod h1:WoEbx8WTcFJfzCe0hbmyTGrfjt8PzNEBdxlNUO24NhA=
|
||||
github.com/bytedance/sonic/loader v0.3.0 h1:dskwH8edlzNMctoruo8FPTJDF3vLtDT0sXZwvZJyqeA=
|
||||
github.com/bytedance/sonic/loader v0.3.0/go.mod h1:N8A3vUdtUebEY2/VQC0MyhYeKUFosQU6FxH2JmUe6VI=
|
||||
github.com/cloudwego/base64x v0.1.6 h1:t11wG9AECkCDk5fMSoxmufanudBtJ+/HemLstXDLI2M=
|
||||
github.com/cloudwego/base64x v0.1.6/go.mod h1:OFcloc187FXDaYHvrNIjxSe8ncn0OOM8gEHfghB2IPU=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/gabriel-vasile/mimetype v1.4.8 h1:FfZ3gj38NjllZIeJAmMhr+qKL8Wu+nOoI3GqacKw1NM=
|
||||
github.com/gabriel-vasile/mimetype v1.4.8/go.mod h1:ByKUIKGjh1ODkGM1asKUbQZOLGrPjydw3hYPU2YU9t8=
|
||||
github.com/gin-contrib/sse v1.1.0 h1:n0w2GMuUpWDVp7qSpvze6fAu9iRxJY4Hmj6AmBOU05w=
|
||||
github.com/gin-contrib/sse v1.1.0/go.mod h1:hxRZ5gVpWMT7Z0B0gSNYqqsSCNIJMjzvm6fqCz9vjwM=
|
||||
github.com/gin-gonic/gin v1.11.0 h1:OW/6PLjyusp2PPXtyxKHU0RbX6I/l28FTdDlae5ueWk=
|
||||
github.com/gin-gonic/gin v1.11.0/go.mod h1:+iq/FyxlGzII0KHiBGjuNn4UNENUlKbGlNmc+W50Dls=
|
||||
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
|
||||
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
|
||||
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
|
||||
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
|
||||
github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
|
||||
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
|
||||
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
|
||||
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
|
||||
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
|
||||
github.com/go-playground/validator/v10 v10.27.0 h1:w8+XrWVMhGkxOaaowyKH35gFydVHOvC0/uWoy2Fzwn4=
|
||||
github.com/go-playground/validator/v10 v10.27.0/go.mod h1:I5QpIEbmr8On7W0TktmJAumgzX4CA1XNl4ZmDuVHKKo=
|
||||
github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
|
||||
github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
|
||||
github.com/goccy/go-yaml v1.18.0 h1:8W7wMFS12Pcas7KU+VVkaiCng+kG8QiFeFwzFb+rwuw=
|
||||
github.com/goccy/go-yaml v1.18.0/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA=
|
||||
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
|
||||
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
|
||||
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
||||
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
||||
github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y=
|
||||
github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
|
||||
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
|
||||
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
|
||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc=
|
||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
|
||||
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
||||
github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4=
|
||||
github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/quic-go/qpack v0.5.1 h1:giqksBPnT/HDtZ6VhtFKgoLOWmlyo9Ei6u9PqzIMbhI=
|
||||
github.com/quic-go/qpack v0.5.1/go.mod h1:+PC4XFrEskIVkcLzpEkbLqq1uCoxPhQuvK5rH1ZgaEg=
|
||||
github.com/quic-go/quic-go v0.54.0 h1:6s1YB9QotYI6Ospeiguknbp2Znb/jZYjZLRXn9kMQBg=
|
||||
github.com/quic-go/quic-go v0.54.0/go.mod h1:e68ZEaCdyviluZmy44P6Iey98v/Wfz6HCjQEm+l8zTY=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
github.com/quic-go/go-ossfuzz-seeds v0.1.0 h1:APacT+iIaNF6fd8AGEiN3bT/Jtkd2jz4v4TzM7MFjy0=
|
||||
github.com/quic-go/go-ossfuzz-seeds v0.1.0/go.mod h1:3IOHRbJIc+L6YKMwfDtJAM9Vj9k0YY4muhuyUYk5tbk=
|
||||
github.com/quic-go/quic-go v0.60.0 h1:xcQioE8OM66UQLeUMHltK1CCcOu3JbVB4JAQdDQSB+0=
|
||||
github.com/quic-go/quic-go v0.60.0/go.mod h1:wpKpjmPpftl30sL6pFh7REVpjbcCVy4zt2vDyK1TuJk=
|
||||
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
||||
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
|
||||
github.com/ugorji/go/codec v1.3.0 h1:Qd2W2sQawAfG8XSvzwhBeoGq71zXOC/Q1E9y/wUcsUA=
|
||||
github.com/ugorji/go/codec v1.3.0/go.mod h1:pRBVtBSKl77K30Bv8R2P+cLSGaTtex6fsA2Wjqmfxj4=
|
||||
go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
|
||||
go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
|
||||
go.opentelemetry.io/otel v1.37.0 h1:9zhNfelUvx0KBfu/gb+ZgeAfAgtWrfHJZcAqFC228wQ=
|
||||
@@ -81,25 +30,16 @@ go.opentelemetry.io/otel/sdk/metric v1.37.0 h1:90lI228XrB9jCMuSdA0673aubgRobVZFh
|
||||
go.opentelemetry.io/otel/sdk/metric v1.37.0/go.mod h1:cNen4ZWfiD37l5NhS+Keb5RXVWZWpRE+9WyVCpbo5ps=
|
||||
go.opentelemetry.io/otel/trace v1.37.0 h1:HLdcFNbRQBE2imdSEgm/kwqmQj1Or1l/7bW6mxVK7z4=
|
||||
go.opentelemetry.io/otel/trace v1.37.0/go.mod h1:TlgrlQ+PtQO5XFerSPUYG0JSgGyryXewPGyayAWSBS0=
|
||||
go.uber.org/mock v0.5.0 h1:KAMbZvZPyBPWgD14IrIQ38QCyjwpvVVV6K/bHl1IwQU=
|
||||
go.uber.org/mock v0.5.0/go.mod h1:ge71pBPLYDk7QIi1LupWxdAykm7KIEFchiOqd6z7qMM=
|
||||
golang.org/x/arch v0.20.0 h1:dx1zTU0MAE98U+TQ8BLl7XsJbgze2WnNKF/8tGp/Q6c=
|
||||
golang.org/x/arch v0.20.0/go.mod h1:bdwinDaKcfZUGpH09BB7ZmOfhalA8lQdzl62l8gGWsk=
|
||||
golang.org/x/crypto v0.40.0 h1:r4x+VvoG5Fm+eJcxMaY8CQM7Lb0l1lsmjGBQ6s8BfKM=
|
||||
golang.org/x/crypto v0.40.0/go.mod h1:Qr1vMER5WyS2dfPHAlsOj01wgLbsyWtFn/aY+5+ZdxY=
|
||||
golang.org/x/mod v0.25.0 h1:n7a+ZbQKQA/Ysbyb0/6IbB1H/X41mKgbhfv7AfG/44w=
|
||||
golang.org/x/mod v0.25.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww=
|
||||
golang.org/x/net v0.42.0 h1:jzkYrhi3YQWD6MLBJcsklgQsoAcw89EcZbJw8Z614hs=
|
||||
golang.org/x/net v0.42.0/go.mod h1:FF1RA5d3u7nAYA4z2TkclSCKh68eSXtiFwcWQpPXdt8=
|
||||
golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw=
|
||||
golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI=
|
||||
golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||
golang.org/x/text v0.27.0 h1:4fGWRpyh641NLlecmyl4LOe6yDdfaYNrGb2zdfo4JV4=
|
||||
golang.org/x/text v0.27.0/go.mod h1:1D28KMCvyooCX9hBiosv5Tz/+YLxj0j7XhWjpSUF7CU=
|
||||
golang.org/x/tools v0.34.0 h1:qIpSLOxeCYGg9TrcJokLBG4KFA6d795g0xkBkiESGlo=
|
||||
golang.org/x/tools v0.34.0/go.mod h1:pAP9OwEaY1CAW3HOmg3hLZC5Z0CCmzjAF2UQMSqNARg=
|
||||
go.uber.org/mock v0.5.2 h1:LbtPTcP8A5k9WPXj54PPPbjcI4Y6lhyOZXn+VS7wNko=
|
||||
go.uber.org/mock v0.5.2/go.mod h1:wLlUxC2vVTPTaE3UD51E0BGOAElKrILxhVSDYQLld5o=
|
||||
golang.org/x/crypto v0.51.0 h1:IBPXwPfKxY7cWQZ38ZCIRPI50YLeevDLlLnyC5wRGTI=
|
||||
golang.org/x/crypto v0.51.0/go.mod h1:8AdwkbraGNABw2kOX6YFPs3WM22XqI4EXEd8g+x7Oc8=
|
||||
golang.org/x/net v0.55.0 h1:bcvxaJn3e1U6InsFWt1JUq1aSjnRxLzT2rtD2KfkDF8=
|
||||
golang.org/x/net v0.55.0/go.mod h1:L5U2KuzuOe1lY7Z+aWVIKK6qEeJXnXV9yzGA+WCHJww=
|
||||
golang.org/x/sys v0.45.0 h1:dO4czNzziLiiXplLQgBCEpCvXQ3dnkn0SdaZSYdQ+FY=
|
||||
golang.org/x/sys v0.45.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
|
||||
golang.org/x/text v0.37.0 h1:Cqjiwd9eSg8e0QAkyCaQTNHFIIzWtidPahFWR83rTrc=
|
||||
golang.org/x/text v0.37.0/go.mod h1:a5sjxXGs9hsn/AJVwuElvCAo9v8QYLzvavO5z2PiM38=
|
||||
gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk=
|
||||
gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250804133106-a7a43d27e69b h1:zPKJod4w6F1+nRGDI9ubnXYhU9NSWoFAijkHkUXeTK8=
|
||||
@@ -108,7 +48,5 @@ google.golang.org/grpc v1.76.0 h1:UnVkv1+uMLYXoIz6o7chp59WfQUYA2ex/BXQ9rHZu7A=
|
||||
google.golang.org/grpc v1.76.0/go.mod h1:Ju12QI8M6iQJtbcsV+awF5a4hfJMLi4X0JLo94ULZ6c=
|
||||
google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE=
|
||||
google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
|
||||
@@ -0,0 +1,99 @@
|
||||
package http3
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"time"
|
||||
|
||||
"grpccanary/lib/grpc/http3/protoapi"
|
||||
|
||||
"github.com/quic-go/quic-go"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/credentials/insecure"
|
||||
)
|
||||
|
||||
func quicDialer(tlsConf *tls.Config) func(context.Context, string) (net.Conn, error) {
|
||||
return func(ctx context.Context, addr string) (net.Conn, error) {
|
||||
qconn, err := quic.DialAddr(ctx, addr, tlsConf, &quic.Config{
|
||||
KeepAlivePeriod: 10 * time.Second,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("quic dial failed: %w", err)
|
||||
}
|
||||
|
||||
stream, err := qconn.OpenStreamSync(ctx)
|
||||
if err != nil {
|
||||
_ = qconn.CloseWithError(0, "failed to open stream")
|
||||
return nil, fmt.Errorf("failed to open stream: %w", err)
|
||||
}
|
||||
|
||||
return &quicNetConn{Stream: stream, conn: qconn}, nil
|
||||
}
|
||||
}
|
||||
|
||||
func ClientRun(addr string) error {
|
||||
tlsConf := &tls.Config{
|
||||
InsecureSkipVerify: true,
|
||||
NextProtos: []string{"grpc-http3-canary"},
|
||||
}
|
||||
|
||||
conn, err := grpc.NewClient(addr,
|
||||
grpc.WithTransportCredentials(insecure.NewCredentials()),
|
||||
grpc.WithContextDialer(quicDialer(tlsConf)),
|
||||
)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create grpc client: %w", err)
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
client := protoapi.NewHttp3ServiceClient(conn)
|
||||
|
||||
// Call 1: First Unary Ping
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||
defer cancel()
|
||||
res1, err := client.Ping(ctx, &protoapi.PingRequest{Message: "First Message"})
|
||||
if err != nil {
|
||||
return fmt.Errorf("unary ping 1 failed: %w", err)
|
||||
}
|
||||
fmt.Printf("[HTTP3 Client] Received Ping 1 Response: Message='%s', Transport='%s'\n", res1.GetMessage(), res1.GetTransport())
|
||||
|
||||
// Call 2: Second Unary Ping (to verify stream multiplexing / reuse over same QUIC connection)
|
||||
ctx2, cancel2 := context.WithTimeout(context.Background(), 5*time.Second)
|
||||
defer cancel2()
|
||||
res2, err := client.Ping(ctx2, &protoapi.PingRequest{Message: "Second Message"})
|
||||
if err != nil {
|
||||
return fmt.Errorf("unary ping 2 failed: %w", err)
|
||||
}
|
||||
fmt.Printf("[HTTP3 Client] Received Ping 2 Response: Message='%s', Transport='%s'\n", res2.GetMessage(), res2.GetTransport())
|
||||
|
||||
// Call 3: Bidirectional Streaming Ping
|
||||
streamCtx, streamCancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||
defer streamCancel()
|
||||
stream, err := client.StreamPing(streamCtx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to open bi-directional stream: %w", err)
|
||||
}
|
||||
|
||||
for i := 1; i <= 3; i++ {
|
||||
msg := fmt.Sprintf("Stream Message %d", i)
|
||||
err := stream.Send(&protoapi.PingRequest{Message: msg})
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to send stream message %d: %w", i, err)
|
||||
}
|
||||
|
||||
res, err := stream.Recv()
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to receive stream response %d: %w", i, err)
|
||||
}
|
||||
fmt.Printf("[HTTP3 Client] Received Stream Response %d: Message='%s', Transport='%s'\n", i, res.GetMessage(), res.GetTransport())
|
||||
}
|
||||
|
||||
_ = stream.CloseSend()
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package http3
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestHttp3ServerClient(t *testing.T) {
|
||||
// Start server on ephemeral port
|
||||
lis, cleanup, err := ServerRun("127.0.0.1:0")
|
||||
if err != nil {
|
||||
t.Fatalf("failed to start server: %v", err)
|
||||
}
|
||||
defer cleanup()
|
||||
|
||||
addr := lis.Addr().String()
|
||||
|
||||
// Wait for server to be fully ready
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
|
||||
// Run client against the server
|
||||
err = ClientRun(addr)
|
||||
if err != nil {
|
||||
t.Fatalf("client run failed: %v", err)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
syntax = "proto3";
|
||||
|
||||
option go_package = "./protoapi/;protoapi";
|
||||
|
||||
service Http3Service {
|
||||
rpc Ping (PingRequest) returns (PingResponse);
|
||||
rpc StreamPing (stream PingRequest) returns (stream PingResponse);
|
||||
}
|
||||
|
||||
message PingRequest {
|
||||
string Message = 1;
|
||||
}
|
||||
|
||||
message PingResponse {
|
||||
string Message = 1;
|
||||
string Transport = 2; // Should return "quic"
|
||||
}
|
||||
@@ -0,0 +1,186 @@
|
||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.36.11
|
||||
// protoc v3.21.12
|
||||
// source: protoapi.proto
|
||||
|
||||
package protoapi
|
||||
|
||||
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)
|
||||
)
|
||||
|
||||
type PingRequest struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
Message string `protobuf:"bytes,1,opt,name=Message,proto3" json:"Message,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *PingRequest) Reset() {
|
||||
*x = PingRequest{}
|
||||
mi := &file_protoapi_proto_msgTypes[0]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
||||
func (x *PingRequest) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*PingRequest) ProtoMessage() {}
|
||||
|
||||
func (x *PingRequest) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_protoapi_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 PingRequest.ProtoReflect.Descriptor instead.
|
||||
func (*PingRequest) Descriptor() ([]byte, []int) {
|
||||
return file_protoapi_proto_rawDescGZIP(), []int{0}
|
||||
}
|
||||
|
||||
func (x *PingRequest) GetMessage() string {
|
||||
if x != nil {
|
||||
return x.Message
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
type PingResponse struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
Message string `protobuf:"bytes,1,opt,name=Message,proto3" json:"Message,omitempty"`
|
||||
Transport string `protobuf:"bytes,2,opt,name=Transport,proto3" json:"Transport,omitempty"` // Should return "quic"
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *PingResponse) Reset() {
|
||||
*x = PingResponse{}
|
||||
mi := &file_protoapi_proto_msgTypes[1]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
||||
func (x *PingResponse) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*PingResponse) ProtoMessage() {}
|
||||
|
||||
func (x *PingResponse) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_protoapi_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 PingResponse.ProtoReflect.Descriptor instead.
|
||||
func (*PingResponse) Descriptor() ([]byte, []int) {
|
||||
return file_protoapi_proto_rawDescGZIP(), []int{1}
|
||||
}
|
||||
|
||||
func (x *PingResponse) GetMessage() string {
|
||||
if x != nil {
|
||||
return x.Message
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *PingResponse) GetTransport() string {
|
||||
if x != nil {
|
||||
return x.Transport
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
var File_protoapi_proto protoreflect.FileDescriptor
|
||||
|
||||
const file_protoapi_proto_rawDesc = "" +
|
||||
"\n" +
|
||||
"\x0eprotoapi.proto\"'\n" +
|
||||
"\vPingRequest\x12\x18\n" +
|
||||
"\aMessage\x18\x01 \x01(\tR\aMessage\"F\n" +
|
||||
"\fPingResponse\x12\x18\n" +
|
||||
"\aMessage\x18\x01 \x01(\tR\aMessage\x12\x1c\n" +
|
||||
"\tTransport\x18\x02 \x01(\tR\tTransport2b\n" +
|
||||
"\fHttp3Service\x12#\n" +
|
||||
"\x04Ping\x12\f.PingRequest\x1a\r.PingResponse\x12-\n" +
|
||||
"\n" +
|
||||
"StreamPing\x12\f.PingRequest\x1a\r.PingResponse(\x010\x01B\x16Z\x14./protoapi/;protoapib\x06proto3"
|
||||
|
||||
var (
|
||||
file_protoapi_proto_rawDescOnce sync.Once
|
||||
file_protoapi_proto_rawDescData []byte
|
||||
)
|
||||
|
||||
func file_protoapi_proto_rawDescGZIP() []byte {
|
||||
file_protoapi_proto_rawDescOnce.Do(func() {
|
||||
file_protoapi_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_protoapi_proto_rawDesc), len(file_protoapi_proto_rawDesc)))
|
||||
})
|
||||
return file_protoapi_proto_rawDescData
|
||||
}
|
||||
|
||||
var file_protoapi_proto_msgTypes = make([]protoimpl.MessageInfo, 2)
|
||||
var file_protoapi_proto_goTypes = []any{
|
||||
(*PingRequest)(nil), // 0: PingRequest
|
||||
(*PingResponse)(nil), // 1: PingResponse
|
||||
}
|
||||
var file_protoapi_proto_depIdxs = []int32{
|
||||
0, // 0: Http3Service.Ping:input_type -> PingRequest
|
||||
0, // 1: Http3Service.StreamPing:input_type -> PingRequest
|
||||
1, // 2: Http3Service.Ping:output_type -> PingResponse
|
||||
1, // 3: Http3Service.StreamPing:output_type -> PingResponse
|
||||
2, // [2:4] is the sub-list for method output_type
|
||||
0, // [0:2] is the sub-list for method input_type
|
||||
0, // [0:0] is the sub-list for extension type_name
|
||||
0, // [0:0] is the sub-list for extension extendee
|
||||
0, // [0:0] is the sub-list for field type_name
|
||||
}
|
||||
|
||||
func init() { file_protoapi_proto_init() }
|
||||
func file_protoapi_proto_init() {
|
||||
if File_protoapi_proto != nil {
|
||||
return
|
||||
}
|
||||
type x struct{}
|
||||
out := protoimpl.TypeBuilder{
|
||||
File: protoimpl.DescBuilder{
|
||||
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
||||
RawDescriptor: unsafe.Slice(unsafe.StringData(file_protoapi_proto_rawDesc), len(file_protoapi_proto_rawDesc)),
|
||||
NumEnums: 0,
|
||||
NumMessages: 2,
|
||||
NumExtensions: 0,
|
||||
NumServices: 1,
|
||||
},
|
||||
GoTypes: file_protoapi_proto_goTypes,
|
||||
DependencyIndexes: file_protoapi_proto_depIdxs,
|
||||
MessageInfos: file_protoapi_proto_msgTypes,
|
||||
}.Build()
|
||||
File_protoapi_proto = out.File
|
||||
file_protoapi_proto_goTypes = nil
|
||||
file_protoapi_proto_depIdxs = nil
|
||||
}
|
||||
@@ -0,0 +1,154 @@
|
||||
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
|
||||
// versions:
|
||||
// - protoc-gen-go-grpc v1.6.2
|
||||
// - protoc v3.21.12
|
||||
// source: protoapi.proto
|
||||
|
||||
package protoapi
|
||||
|
||||
import (
|
||||
context "context"
|
||||
grpc "google.golang.org/grpc"
|
||||
codes "google.golang.org/grpc/codes"
|
||||
status "google.golang.org/grpc/status"
|
||||
)
|
||||
|
||||
// This is a compile-time assertion to ensure that this generated file
|
||||
// is compatible with the grpc package it is being compiled against.
|
||||
// Requires gRPC-Go v1.64.0 or later.
|
||||
const _ = grpc.SupportPackageIsVersion9
|
||||
|
||||
const (
|
||||
Http3Service_Ping_FullMethodName = "/Http3Service/Ping"
|
||||
Http3Service_StreamPing_FullMethodName = "/Http3Service/StreamPing"
|
||||
)
|
||||
|
||||
// Http3ServiceClient is the client API for Http3Service service.
|
||||
//
|
||||
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream.
|
||||
type Http3ServiceClient interface {
|
||||
Ping(ctx context.Context, in *PingRequest, opts ...grpc.CallOption) (*PingResponse, error)
|
||||
StreamPing(ctx context.Context, opts ...grpc.CallOption) (grpc.BidiStreamingClient[PingRequest, PingResponse], error)
|
||||
}
|
||||
|
||||
type http3ServiceClient struct {
|
||||
cc grpc.ClientConnInterface
|
||||
}
|
||||
|
||||
func NewHttp3ServiceClient(cc grpc.ClientConnInterface) Http3ServiceClient {
|
||||
return &http3ServiceClient{cc}
|
||||
}
|
||||
|
||||
func (c *http3ServiceClient) Ping(ctx context.Context, in *PingRequest, opts ...grpc.CallOption) (*PingResponse, error) {
|
||||
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
||||
out := new(PingResponse)
|
||||
err := c.cc.Invoke(ctx, Http3Service_Ping_FullMethodName, in, out, cOpts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *http3ServiceClient) StreamPing(ctx context.Context, opts ...grpc.CallOption) (grpc.BidiStreamingClient[PingRequest, PingResponse], error) {
|
||||
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
||||
stream, err := c.cc.NewStream(ctx, &Http3Service_ServiceDesc.Streams[0], Http3Service_StreamPing_FullMethodName, cOpts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
x := &grpc.GenericClientStream[PingRequest, PingResponse]{ClientStream: stream}
|
||||
return x, nil
|
||||
}
|
||||
|
||||
// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name.
|
||||
type Http3Service_StreamPingClient = grpc.BidiStreamingClient[PingRequest, PingResponse]
|
||||
|
||||
// Http3ServiceServer is the server API for Http3Service service.
|
||||
// All implementations must embed UnimplementedHttp3ServiceServer
|
||||
// for forward compatibility.
|
||||
type Http3ServiceServer interface {
|
||||
Ping(context.Context, *PingRequest) (*PingResponse, error)
|
||||
StreamPing(grpc.BidiStreamingServer[PingRequest, PingResponse]) error
|
||||
mustEmbedUnimplementedHttp3ServiceServer()
|
||||
}
|
||||
|
||||
// UnimplementedHttp3ServiceServer must be embedded to have
|
||||
// forward compatible implementations.
|
||||
//
|
||||
// NOTE: this should be embedded by value instead of pointer to avoid a nil
|
||||
// pointer dereference when methods are called.
|
||||
type UnimplementedHttp3ServiceServer struct{}
|
||||
|
||||
func (UnimplementedHttp3ServiceServer) Ping(context.Context, *PingRequest) (*PingResponse, error) {
|
||||
return nil, status.Error(codes.Unimplemented, "method Ping not implemented")
|
||||
}
|
||||
func (UnimplementedHttp3ServiceServer) StreamPing(grpc.BidiStreamingServer[PingRequest, PingResponse]) error {
|
||||
return status.Error(codes.Unimplemented, "method StreamPing not implemented")
|
||||
}
|
||||
func (UnimplementedHttp3ServiceServer) mustEmbedUnimplementedHttp3ServiceServer() {}
|
||||
func (UnimplementedHttp3ServiceServer) testEmbeddedByValue() {}
|
||||
|
||||
// UnsafeHttp3ServiceServer may be embedded to opt out of forward compatibility for this service.
|
||||
// Use of this interface is not recommended, as added methods to Http3ServiceServer will
|
||||
// result in compilation errors.
|
||||
type UnsafeHttp3ServiceServer interface {
|
||||
mustEmbedUnimplementedHttp3ServiceServer()
|
||||
}
|
||||
|
||||
func RegisterHttp3ServiceServer(s grpc.ServiceRegistrar, srv Http3ServiceServer) {
|
||||
// If the following call panics, it indicates UnimplementedHttp3ServiceServer was
|
||||
// embedded by pointer and is nil. This will cause panics if an
|
||||
// unimplemented method is ever invoked, so we test this at initialization
|
||||
// time to prevent it from happening at runtime later due to I/O.
|
||||
if t, ok := srv.(interface{ testEmbeddedByValue() }); ok {
|
||||
t.testEmbeddedByValue()
|
||||
}
|
||||
s.RegisterService(&Http3Service_ServiceDesc, srv)
|
||||
}
|
||||
|
||||
func _Http3Service_Ping_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(PingRequest)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(Http3ServiceServer).Ping(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: Http3Service_Ping_FullMethodName,
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(Http3ServiceServer).Ping(ctx, req.(*PingRequest))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _Http3Service_StreamPing_Handler(srv interface{}, stream grpc.ServerStream) error {
|
||||
return srv.(Http3ServiceServer).StreamPing(&grpc.GenericServerStream[PingRequest, PingResponse]{ServerStream: stream})
|
||||
}
|
||||
|
||||
// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name.
|
||||
type Http3Service_StreamPingServer = grpc.BidiStreamingServer[PingRequest, PingResponse]
|
||||
|
||||
// Http3Service_ServiceDesc is the grpc.ServiceDesc for Http3Service service.
|
||||
// It's only intended for direct use with grpc.RegisterService,
|
||||
// and not to be introspected or modified (even as a copy)
|
||||
var Http3Service_ServiceDesc = grpc.ServiceDesc{
|
||||
ServiceName: "Http3Service",
|
||||
HandlerType: (*Http3ServiceServer)(nil),
|
||||
Methods: []grpc.MethodDesc{
|
||||
{
|
||||
MethodName: "Ping",
|
||||
Handler: _Http3Service_Ping_Handler,
|
||||
},
|
||||
},
|
||||
Streams: []grpc.StreamDesc{
|
||||
{
|
||||
StreamName: "StreamPing",
|
||||
Handler: _Http3Service_StreamPing_Handler,
|
||||
ServerStreams: true,
|
||||
ClientStreams: true,
|
||||
},
|
||||
},
|
||||
Metadata: "protoapi.proto",
|
||||
}
|
||||
@@ -0,0 +1,212 @@
|
||||
package http3
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"crypto/x509/pkix"
|
||||
"encoding/pem"
|
||||
"fmt"
|
||||
"io"
|
||||
"math/big"
|
||||
"net"
|
||||
"time"
|
||||
|
||||
"grpccanary/lib/grpc/http3/protoapi"
|
||||
|
||||
"github.com/quic-go/quic-go"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/reflection"
|
||||
)
|
||||
|
||||
// 1. self-signed certificate generation
|
||||
func generateTLSConfig() (*tls.Config, error) {
|
||||
key, err := rsa.GenerateKey(rand.Reader, 2048)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
template := x509.Certificate{
|
||||
SerialNumber: big.NewInt(1),
|
||||
Subject: pkix.Name{
|
||||
Organization: []string{"gRPC HTTP3 Canary"},
|
||||
},
|
||||
NotBefore: time.Now(),
|
||||
NotAfter: time.Now().Add(365 * 24 * time.Hour),
|
||||
KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
|
||||
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
|
||||
DNSNames: []string{"localhost"},
|
||||
IPAddresses: []net.IP{net.ParseIP("127.0.0.1"), net.ParseIP("::1")},
|
||||
}
|
||||
|
||||
certDER, err := x509.CreateCertificate(rand.Reader, &template, &template, &key.PublicKey, key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
certPEM := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: certDER})
|
||||
|
||||
privBytes, err := x509.MarshalPKCS8PrivateKey(key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
keyPEM := pem.EncodeToMemory(&pem.Block{Type: "PRIVATE KEY", Bytes: privBytes})
|
||||
|
||||
tlsCert, err := tls.X509KeyPair(certPEM, keyPEM)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &tls.Config{
|
||||
Certificates: []tls.Certificate{tlsCert},
|
||||
NextProtos: []string{"grpc-http3-canary"},
|
||||
}, nil
|
||||
}
|
||||
|
||||
// 2. net.Conn wrapper for quic.Stream
|
||||
type quicNetConn struct {
|
||||
*quic.Stream
|
||||
conn *quic.Conn
|
||||
}
|
||||
|
||||
func (c *quicNetConn) LocalAddr() net.Addr {
|
||||
return c.conn.LocalAddr()
|
||||
}
|
||||
|
||||
func (c *quicNetConn) RemoteAddr() net.Addr {
|
||||
return c.conn.RemoteAddr()
|
||||
}
|
||||
|
||||
// 3. net.Listener wrapper for quic.Listener using non-blocking channels
|
||||
type quicListener struct {
|
||||
lis *quic.Listener
|
||||
connChan chan net.Conn
|
||||
errChan chan error
|
||||
ctx context.Context
|
||||
cancel context.CancelFunc
|
||||
}
|
||||
|
||||
func NewQuicListener(lis *quic.Listener) *quicListener {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
ql := &quicListener{
|
||||
lis: lis,
|
||||
connChan: make(chan net.Conn, 100),
|
||||
errChan: make(chan error, 10),
|
||||
ctx: ctx,
|
||||
cancel: cancel,
|
||||
}
|
||||
go ql.listenLoop()
|
||||
return ql
|
||||
}
|
||||
|
||||
func (ql *quicListener) listenLoop() {
|
||||
for {
|
||||
qconn, err := ql.lis.Accept(ql.ctx)
|
||||
if err != nil {
|
||||
select {
|
||||
case ql.errChan <- err:
|
||||
default:
|
||||
}
|
||||
return
|
||||
}
|
||||
go ql.acceptStreams(qconn)
|
||||
}
|
||||
}
|
||||
|
||||
func (ql *quicListener) acceptStreams(qconn *quic.Conn) {
|
||||
for {
|
||||
stream, err := qconn.AcceptStream(ql.ctx)
|
||||
if err != nil {
|
||||
// Stop checking this connection when it closes
|
||||
return
|
||||
}
|
||||
ql.connChan <- &quicNetConn{Stream: stream, conn: qconn}
|
||||
}
|
||||
}
|
||||
|
||||
func (ql *quicListener) Accept() (net.Conn, error) {
|
||||
select {
|
||||
case conn := <-ql.connChan:
|
||||
return conn, nil
|
||||
case err := <-ql.errChan:
|
||||
return nil, err
|
||||
case <-ql.ctx.Done():
|
||||
return nil, io.EOF
|
||||
}
|
||||
}
|
||||
|
||||
func (ql *quicListener) Close() error {
|
||||
ql.cancel()
|
||||
return ql.lis.Close()
|
||||
}
|
||||
|
||||
func (ql *quicListener) Addr() net.Addr {
|
||||
return ql.lis.Addr()
|
||||
}
|
||||
|
||||
// 4. Http3Service Server Implementation
|
||||
type Http3Server struct {
|
||||
protoapi.UnimplementedHttp3ServiceServer
|
||||
}
|
||||
|
||||
func (Http3Server) Ping(ctx context.Context, r *protoapi.PingRequest) (*protoapi.PingResponse, error) {
|
||||
return &protoapi.PingResponse{
|
||||
Message: "Pong: " + r.GetMessage(),
|
||||
Transport: "quic",
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (Http3Server) StreamPing(stream protoapi.Http3Service_StreamPingServer) error {
|
||||
for {
|
||||
req, err := stream.Recv()
|
||||
if err == io.EOF {
|
||||
return nil
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = stream.Send(&protoapi.PingResponse{
|
||||
Message: "Pong Stream: " + req.GetMessage(),
|
||||
Transport: "quic",
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func ServerRun(addr string) (*quic.Listener, func(), error) {
|
||||
tlsConf, err := generateTLSConfig()
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
lis, err := quic.ListenAddr(addr, tlsConf, &quic.Config{
|
||||
KeepAlivePeriod: 10 * time.Second,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
qlis := NewQuicListener(lis)
|
||||
server := grpc.NewServer()
|
||||
protoapi.RegisterHttp3ServiceServer(server, Http3Server{})
|
||||
|
||||
reflection.Register(server)
|
||||
|
||||
go func() {
|
||||
fmt.Printf("[HTTP3 Server] Serving gRPC on UDP/QUIC %s...\n", addr)
|
||||
if err := server.Serve(qlis); err != nil {
|
||||
fmt.Println("[HTTP3 Server] Server closed:", err)
|
||||
}
|
||||
}()
|
||||
|
||||
cleanup := func() {
|
||||
server.GracefulStop()
|
||||
qlis.Close()
|
||||
}
|
||||
|
||||
return lis, cleanup, nil
|
||||
}
|
||||
@@ -0,0 +1,210 @@
|
||||
package modular
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"math/rand"
|
||||
"time"
|
||||
|
||||
"grpccanary/lib/grpc/modular/protoapi"
|
||||
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/credentials/insecure"
|
||||
)
|
||||
|
||||
func AskingDateTime(ctx context.Context, m protoapi.CoreServiceClient) (*protoapi.DateTime, error) {
|
||||
request := &protoapi.RequestDateTime{
|
||||
Value: "Please send me the date and time",
|
||||
}
|
||||
return m.GetDate(ctx, request)
|
||||
}
|
||||
|
||||
func AskPass(ctx context.Context, m protoapi.CoreServiceClient, seed int64, length int64) (*protoapi.RandomPass, error) {
|
||||
request := &protoapi.RequestPass{
|
||||
Seed: seed,
|
||||
Length: length,
|
||||
}
|
||||
return m.GetRandomPass(ctx, request)
|
||||
}
|
||||
|
||||
func AskUpdateSensingData(ctx context.Context, m protoapi.CoreServiceClient, deviceId string, temp float64, humid float64) (*protoapi.SensingResponse, error) {
|
||||
request := &protoapi.SensingData{
|
||||
DeviceId: deviceId,
|
||||
Temperature: temp,
|
||||
Humidity: humid,
|
||||
}
|
||||
return m.UpdateSensingData(ctx, request)
|
||||
}
|
||||
|
||||
func AskUploadFile(ctx context.Context, m protoapi.FileTransferServiceClient, fileName string, fileData []byte) (*protoapi.UploadStatus, error) {
|
||||
stream, err := m.UploadFile(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
chunkSize := 1024
|
||||
totalBytes := len(fileData)
|
||||
|
||||
for i := 0; i < totalBytes; i += chunkSize {
|
||||
end := i + chunkSize
|
||||
if end > totalBytes {
|
||||
end = totalBytes
|
||||
}
|
||||
|
||||
err := stream.Send(&protoapi.FileChunk{
|
||||
FileName: fileName,
|
||||
Content: fileData[i:end],
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return stream.CloseAndRecv()
|
||||
}
|
||||
|
||||
func AskListFiles(ctx context.Context, m protoapi.FileTransferServiceClient) (*protoapi.FileList, error) {
|
||||
return m.ListFiles(ctx, &protoapi.EmptyRequest{})
|
||||
}
|
||||
|
||||
func AskDownloadFile(ctx context.Context, m protoapi.FileTransferServiceClient, fileName string) ([]byte, error) {
|
||||
stream, err := m.DownloadFile(ctx, &protoapi.DownloadRequest{FileName: fileName})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var buffer []byte
|
||||
for {
|
||||
chunk, err := stream.Recv()
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
buffer = append(buffer, chunk.GetContent()...)
|
||||
}
|
||||
|
||||
return buffer, nil
|
||||
}
|
||||
|
||||
func AskSubscribeAlerts(ctx context.Context, m protoapi.AlertServiceClient, clientId string, topic string) {
|
||||
stream, err := m.SubscribeAlerts(ctx, &protoapi.AlertSubscription{
|
||||
ClientId: clientId,
|
||||
Topic: topic,
|
||||
})
|
||||
if err != nil {
|
||||
fmt.Println("[Modular Client] Failed to subscribe alerts:", err)
|
||||
return
|
||||
}
|
||||
|
||||
for {
|
||||
alert, err := stream.Recv()
|
||||
if err == io.EOF {
|
||||
fmt.Println("[Modular Client] Alert subscription stream closed by server.")
|
||||
break
|
||||
}
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
fmt.Printf("\n🔔 [Modular ALERT RECEIVED] ID: %s | Device: %s | Msg: %s | Time: %s\n\n",
|
||||
alert.GetAlertId(), alert.GetDeviceId(), alert.GetMessage(),
|
||||
time.Unix(alert.GetTimestamp(), 0).Format("15:04:05"))
|
||||
}
|
||||
}
|
||||
|
||||
func ClientRun(addr string) {
|
||||
conn, err := grpc.NewClient(addr, grpc.WithTransportCredentials(insecure.NewCredentials()))
|
||||
if err != nil {
|
||||
fmt.Println("[Modular Client] NewClient error:", err)
|
||||
return
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
coreClient := protoapi.NewCoreServiceClient(conn)
|
||||
fileClient := protoapi.NewFileTransferServiceClient(conn)
|
||||
alertClient := protoapi.NewAlertServiceClient(conn)
|
||||
|
||||
// Background Subscription
|
||||
alertCtx, alertCancel := context.WithCancel(context.Background())
|
||||
defer alertCancel()
|
||||
go AskSubscribeAlerts(alertCtx, alertClient, "modular-client-01", "temperature_warnings")
|
||||
time.Sleep(50 * time.Millisecond)
|
||||
|
||||
r, err := AskingDateTime(context.Background(), coreClient)
|
||||
if err != nil {
|
||||
fmt.Println("[Modular Client] GetDate error:", err)
|
||||
return
|
||||
}
|
||||
fmt.Println("[Modular Client] Server Date and Time:", r.Value)
|
||||
|
||||
length := int64(rand.Intn(20))
|
||||
p, err := AskPass(context.Background(), coreClient, 100, length+1)
|
||||
if err != nil {
|
||||
fmt.Println("[Modular Client] GetRandomPass error:", err)
|
||||
return
|
||||
}
|
||||
fmt.Println("[Modular Client] Random Password:", p.Password)
|
||||
|
||||
res, err := AskUpdateSensingData(context.Background(), coreClient, "modular-sensor-01", 24.5, 52.3)
|
||||
if err != nil {
|
||||
fmt.Println("[Modular Client] UpdateSensingData error:", err)
|
||||
return
|
||||
}
|
||||
fmt.Println("[Modular Client] Sensing Update Success:", res.Success)
|
||||
|
||||
// File Upload
|
||||
dummyData := make([]byte, 10240)
|
||||
for i := range dummyData {
|
||||
dummyData[i] = byte(rand.Intn(256))
|
||||
}
|
||||
fmt.Println("[Modular Client] Uploading dummy file...")
|
||||
status, err := AskUploadFile(context.Background(), fileClient, "firmware_modular.bin", dummyData)
|
||||
if err != nil {
|
||||
fmt.Println("[Modular Client] File upload failed:", err)
|
||||
return
|
||||
}
|
||||
fmt.Println("[Modular Client] Upload Success:", status.Success)
|
||||
|
||||
// File List
|
||||
list, err := AskListFiles(context.Background(), fileClient)
|
||||
if err != nil {
|
||||
fmt.Println("[Modular Client] ListFiles failed:", err)
|
||||
return
|
||||
}
|
||||
for i, f := range list.GetFiles() {
|
||||
fmt.Printf("[Modular Client] File [%d]: %s, size: %d\n", i+1, f.GetFileName(), f.GetFileSize())
|
||||
}
|
||||
|
||||
// File Download & Verification
|
||||
downloadedData, err := AskDownloadFile(context.Background(), fileClient, "firmware_modular.bin")
|
||||
if err != nil {
|
||||
fmt.Println("[Modular Client] Download failed:", err)
|
||||
return
|
||||
}
|
||||
fmt.Printf("[Modular Client] Downloaded %d bytes.\n", len(downloadedData))
|
||||
|
||||
isMatch := true
|
||||
if len(dummyData) != len(downloadedData) {
|
||||
isMatch = false
|
||||
} else {
|
||||
for i := range dummyData {
|
||||
if dummyData[i] != downloadedData[i] {
|
||||
isMatch = false
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
fmt.Printf("[Modular Client] Data Integrity Checked: %t\n", isMatch)
|
||||
|
||||
// Trigger alert
|
||||
fmt.Println("[Modular Client] Triggering abnormal high-temperature...")
|
||||
_, err = AskUpdateSensingData(context.Background(), coreClient, "modular-sensor-01", 45.8, 60.1)
|
||||
if err != nil {
|
||||
fmt.Println("[Modular Client] UpdateSensingData (abnormal) error:", err)
|
||||
return
|
||||
}
|
||||
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package modular
|
||||
|
||||
import (
|
||||
"net"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestModularServerClient(t *testing.T) {
|
||||
// Find an ephemeral port
|
||||
lis, err := net.Listen("tcp", "127.0.0.1:0")
|
||||
if err != nil {
|
||||
t.Fatalf("failed to listen: %v", err)
|
||||
}
|
||||
addr := lis.Addr().String()
|
||||
lis.Close()
|
||||
|
||||
// Start server in background
|
||||
go ServerRun(addr)
|
||||
|
||||
// Wait for server to start
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
|
||||
// Run client against the server
|
||||
ClientRun(addr)
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
syntax = "proto3";
|
||||
|
||||
option go_package = "./protoapi/;protoapi";
|
||||
|
||||
import "common.proto";
|
||||
|
||||
service AlertService {
|
||||
rpc SubscribeAlerts (AlertSubscription) returns (stream AlertMessage);
|
||||
}
|
||||
|
||||
message AlertSubscription {
|
||||
string ClientId = 1;
|
||||
string Topic = 2;
|
||||
}
|
||||
|
||||
message AlertMessage {
|
||||
string AlertId = 1;
|
||||
string DeviceId = 2;
|
||||
string Message = 3;
|
||||
int64 Timestamp = 4;
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
syntax = "proto3";
|
||||
|
||||
option go_package = "./protoapi/;protoapi";
|
||||
|
||||
message DateTime {
|
||||
string Value = 1;
|
||||
}
|
||||
|
||||
message RequestDateTime {
|
||||
string Value = 2;
|
||||
}
|
||||
|
||||
message RequestPass {
|
||||
int64 Seed = 1;
|
||||
int64 Length = 8;
|
||||
}
|
||||
|
||||
message RandomPass {
|
||||
string Password = 1;
|
||||
}
|
||||
|
||||
message EmptyRequest {}
|
||||
@@ -0,0 +1,22 @@
|
||||
syntax = "proto3";
|
||||
|
||||
option go_package = "./protoapi/;protoapi";
|
||||
|
||||
import "common.proto";
|
||||
|
||||
service CoreService {
|
||||
rpc GetDate (RequestDateTime) returns (DateTime);
|
||||
rpc UpdateSensingData (SensingData) returns (SensingResponse);
|
||||
rpc GetRandomPass (RequestPass) returns (RandomPass);
|
||||
}
|
||||
|
||||
message SensingData {
|
||||
string DeviceId = 1;
|
||||
double Temperature = 2;
|
||||
double Humidity = 3;
|
||||
}
|
||||
|
||||
message SensingResponse {
|
||||
bool Success = 1;
|
||||
string Message = 2;
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
syntax = "proto3";
|
||||
|
||||
option go_package = "./protoapi/;protoapi";
|
||||
|
||||
import "common.proto";
|
||||
|
||||
service FileTransferService {
|
||||
rpc UploadFile (stream FileChunk) returns (UploadStatus);
|
||||
rpc ListFiles (EmptyRequest) returns (FileList);
|
||||
rpc DownloadFile (DownloadRequest) returns (stream FileChunk);
|
||||
}
|
||||
|
||||
message FileChunk {
|
||||
string FileName = 1;
|
||||
bytes Content = 2;
|
||||
}
|
||||
|
||||
message UploadStatus {
|
||||
bool Success = 1;
|
||||
string Message = 2;
|
||||
int64 BytesUploaded = 3;
|
||||
}
|
||||
|
||||
message FileMetadata {
|
||||
string FileName = 1;
|
||||
int64 FileSize = 2;
|
||||
int64 UploadedAt = 3;
|
||||
}
|
||||
|
||||
message FileList {
|
||||
repeated FileMetadata Files = 1;
|
||||
}
|
||||
|
||||
message DownloadRequest {
|
||||
string FileName = 1;
|
||||
}
|
||||
@@ -0,0 +1,210 @@
|
||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.36.11
|
||||
// protoc v3.21.12
|
||||
// source: alerts.proto
|
||||
|
||||
package protoapi
|
||||
|
||||
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)
|
||||
)
|
||||
|
||||
type AlertSubscription struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
ClientId string `protobuf:"bytes,1,opt,name=ClientId,proto3" json:"ClientId,omitempty"`
|
||||
Topic string `protobuf:"bytes,2,opt,name=Topic,proto3" json:"Topic,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *AlertSubscription) Reset() {
|
||||
*x = AlertSubscription{}
|
||||
mi := &file_alerts_proto_msgTypes[0]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
||||
func (x *AlertSubscription) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*AlertSubscription) ProtoMessage() {}
|
||||
|
||||
func (x *AlertSubscription) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_alerts_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 AlertSubscription.ProtoReflect.Descriptor instead.
|
||||
func (*AlertSubscription) Descriptor() ([]byte, []int) {
|
||||
return file_alerts_proto_rawDescGZIP(), []int{0}
|
||||
}
|
||||
|
||||
func (x *AlertSubscription) GetClientId() string {
|
||||
if x != nil {
|
||||
return x.ClientId
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *AlertSubscription) GetTopic() string {
|
||||
if x != nil {
|
||||
return x.Topic
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
type AlertMessage struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
AlertId string `protobuf:"bytes,1,opt,name=AlertId,proto3" json:"AlertId,omitempty"`
|
||||
DeviceId string `protobuf:"bytes,2,opt,name=DeviceId,proto3" json:"DeviceId,omitempty"`
|
||||
Message string `protobuf:"bytes,3,opt,name=Message,proto3" json:"Message,omitempty"`
|
||||
Timestamp int64 `protobuf:"varint,4,opt,name=Timestamp,proto3" json:"Timestamp,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *AlertMessage) Reset() {
|
||||
*x = AlertMessage{}
|
||||
mi := &file_alerts_proto_msgTypes[1]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
||||
func (x *AlertMessage) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*AlertMessage) ProtoMessage() {}
|
||||
|
||||
func (x *AlertMessage) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_alerts_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 AlertMessage.ProtoReflect.Descriptor instead.
|
||||
func (*AlertMessage) Descriptor() ([]byte, []int) {
|
||||
return file_alerts_proto_rawDescGZIP(), []int{1}
|
||||
}
|
||||
|
||||
func (x *AlertMessage) GetAlertId() string {
|
||||
if x != nil {
|
||||
return x.AlertId
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *AlertMessage) GetDeviceId() string {
|
||||
if x != nil {
|
||||
return x.DeviceId
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *AlertMessage) GetMessage() string {
|
||||
if x != nil {
|
||||
return x.Message
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *AlertMessage) GetTimestamp() int64 {
|
||||
if x != nil {
|
||||
return x.Timestamp
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
var File_alerts_proto protoreflect.FileDescriptor
|
||||
|
||||
const file_alerts_proto_rawDesc = "" +
|
||||
"\n" +
|
||||
"\falerts.proto\x1a\fcommon.proto\"E\n" +
|
||||
"\x11AlertSubscription\x12\x1a\n" +
|
||||
"\bClientId\x18\x01 \x01(\tR\bClientId\x12\x14\n" +
|
||||
"\x05Topic\x18\x02 \x01(\tR\x05Topic\"|\n" +
|
||||
"\fAlertMessage\x12\x18\n" +
|
||||
"\aAlertId\x18\x01 \x01(\tR\aAlertId\x12\x1a\n" +
|
||||
"\bDeviceId\x18\x02 \x01(\tR\bDeviceId\x12\x18\n" +
|
||||
"\aMessage\x18\x03 \x01(\tR\aMessage\x12\x1c\n" +
|
||||
"\tTimestamp\x18\x04 \x01(\x03R\tTimestamp2F\n" +
|
||||
"\fAlertService\x126\n" +
|
||||
"\x0fSubscribeAlerts\x12\x12.AlertSubscription\x1a\r.AlertMessage0\x01B\x16Z\x14./protoapi/;protoapib\x06proto3"
|
||||
|
||||
var (
|
||||
file_alerts_proto_rawDescOnce sync.Once
|
||||
file_alerts_proto_rawDescData []byte
|
||||
)
|
||||
|
||||
func file_alerts_proto_rawDescGZIP() []byte {
|
||||
file_alerts_proto_rawDescOnce.Do(func() {
|
||||
file_alerts_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_alerts_proto_rawDesc), len(file_alerts_proto_rawDesc)))
|
||||
})
|
||||
return file_alerts_proto_rawDescData
|
||||
}
|
||||
|
||||
var file_alerts_proto_msgTypes = make([]protoimpl.MessageInfo, 2)
|
||||
var file_alerts_proto_goTypes = []any{
|
||||
(*AlertSubscription)(nil), // 0: AlertSubscription
|
||||
(*AlertMessage)(nil), // 1: AlertMessage
|
||||
}
|
||||
var file_alerts_proto_depIdxs = []int32{
|
||||
0, // 0: AlertService.SubscribeAlerts:input_type -> AlertSubscription
|
||||
1, // 1: AlertService.SubscribeAlerts:output_type -> AlertMessage
|
||||
1, // [1:2] is the sub-list for method output_type
|
||||
0, // [0:1] is the sub-list for method input_type
|
||||
0, // [0:0] is the sub-list for extension type_name
|
||||
0, // [0:0] is the sub-list for extension extendee
|
||||
0, // [0:0] is the sub-list for field type_name
|
||||
}
|
||||
|
||||
func init() { file_alerts_proto_init() }
|
||||
func file_alerts_proto_init() {
|
||||
if File_alerts_proto != nil {
|
||||
return
|
||||
}
|
||||
file_common_proto_init()
|
||||
type x struct{}
|
||||
out := protoimpl.TypeBuilder{
|
||||
File: protoimpl.DescBuilder{
|
||||
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
||||
RawDescriptor: unsafe.Slice(unsafe.StringData(file_alerts_proto_rawDesc), len(file_alerts_proto_rawDesc)),
|
||||
NumEnums: 0,
|
||||
NumMessages: 2,
|
||||
NumExtensions: 0,
|
||||
NumServices: 1,
|
||||
},
|
||||
GoTypes: file_alerts_proto_goTypes,
|
||||
DependencyIndexes: file_alerts_proto_depIdxs,
|
||||
MessageInfos: file_alerts_proto_msgTypes,
|
||||
}.Build()
|
||||
File_alerts_proto = out.File
|
||||
file_alerts_proto_goTypes = nil
|
||||
file_alerts_proto_depIdxs = nil
|
||||
}
|
||||
@@ -0,0 +1,124 @@
|
||||
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
|
||||
// versions:
|
||||
// - protoc-gen-go-grpc v1.6.2
|
||||
// - protoc v3.21.12
|
||||
// source: alerts.proto
|
||||
|
||||
package protoapi
|
||||
|
||||
import (
|
||||
context "context"
|
||||
grpc "google.golang.org/grpc"
|
||||
codes "google.golang.org/grpc/codes"
|
||||
status "google.golang.org/grpc/status"
|
||||
)
|
||||
|
||||
// This is a compile-time assertion to ensure that this generated file
|
||||
// is compatible with the grpc package it is being compiled against.
|
||||
// Requires gRPC-Go v1.64.0 or later.
|
||||
const _ = grpc.SupportPackageIsVersion9
|
||||
|
||||
const (
|
||||
AlertService_SubscribeAlerts_FullMethodName = "/AlertService/SubscribeAlerts"
|
||||
)
|
||||
|
||||
// AlertServiceClient is the client API for AlertService service.
|
||||
//
|
||||
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream.
|
||||
type AlertServiceClient interface {
|
||||
SubscribeAlerts(ctx context.Context, in *AlertSubscription, opts ...grpc.CallOption) (grpc.ServerStreamingClient[AlertMessage], error)
|
||||
}
|
||||
|
||||
type alertServiceClient struct {
|
||||
cc grpc.ClientConnInterface
|
||||
}
|
||||
|
||||
func NewAlertServiceClient(cc grpc.ClientConnInterface) AlertServiceClient {
|
||||
return &alertServiceClient{cc}
|
||||
}
|
||||
|
||||
func (c *alertServiceClient) SubscribeAlerts(ctx context.Context, in *AlertSubscription, opts ...grpc.CallOption) (grpc.ServerStreamingClient[AlertMessage], error) {
|
||||
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
||||
stream, err := c.cc.NewStream(ctx, &AlertService_ServiceDesc.Streams[0], AlertService_SubscribeAlerts_FullMethodName, cOpts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
x := &grpc.GenericClientStream[AlertSubscription, AlertMessage]{ClientStream: stream}
|
||||
if err := x.ClientStream.SendMsg(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := x.ClientStream.CloseSend(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return x, nil
|
||||
}
|
||||
|
||||
// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name.
|
||||
type AlertService_SubscribeAlertsClient = grpc.ServerStreamingClient[AlertMessage]
|
||||
|
||||
// AlertServiceServer is the server API for AlertService service.
|
||||
// All implementations must embed UnimplementedAlertServiceServer
|
||||
// for forward compatibility.
|
||||
type AlertServiceServer interface {
|
||||
SubscribeAlerts(*AlertSubscription, grpc.ServerStreamingServer[AlertMessage]) error
|
||||
mustEmbedUnimplementedAlertServiceServer()
|
||||
}
|
||||
|
||||
// UnimplementedAlertServiceServer must be embedded to have
|
||||
// forward compatible implementations.
|
||||
//
|
||||
// NOTE: this should be embedded by value instead of pointer to avoid a nil
|
||||
// pointer dereference when methods are called.
|
||||
type UnimplementedAlertServiceServer struct{}
|
||||
|
||||
func (UnimplementedAlertServiceServer) SubscribeAlerts(*AlertSubscription, grpc.ServerStreamingServer[AlertMessage]) error {
|
||||
return status.Error(codes.Unimplemented, "method SubscribeAlerts not implemented")
|
||||
}
|
||||
func (UnimplementedAlertServiceServer) mustEmbedUnimplementedAlertServiceServer() {}
|
||||
func (UnimplementedAlertServiceServer) testEmbeddedByValue() {}
|
||||
|
||||
// UnsafeAlertServiceServer may be embedded to opt out of forward compatibility for this service.
|
||||
// Use of this interface is not recommended, as added methods to AlertServiceServer will
|
||||
// result in compilation errors.
|
||||
type UnsafeAlertServiceServer interface {
|
||||
mustEmbedUnimplementedAlertServiceServer()
|
||||
}
|
||||
|
||||
func RegisterAlertServiceServer(s grpc.ServiceRegistrar, srv AlertServiceServer) {
|
||||
// If the following call panics, it indicates UnimplementedAlertServiceServer was
|
||||
// embedded by pointer and is nil. This will cause panics if an
|
||||
// unimplemented method is ever invoked, so we test this at initialization
|
||||
// time to prevent it from happening at runtime later due to I/O.
|
||||
if t, ok := srv.(interface{ testEmbeddedByValue() }); ok {
|
||||
t.testEmbeddedByValue()
|
||||
}
|
||||
s.RegisterService(&AlertService_ServiceDesc, srv)
|
||||
}
|
||||
|
||||
func _AlertService_SubscribeAlerts_Handler(srv interface{}, stream grpc.ServerStream) error {
|
||||
m := new(AlertSubscription)
|
||||
if err := stream.RecvMsg(m); err != nil {
|
||||
return err
|
||||
}
|
||||
return srv.(AlertServiceServer).SubscribeAlerts(m, &grpc.GenericServerStream[AlertSubscription, AlertMessage]{ServerStream: stream})
|
||||
}
|
||||
|
||||
// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name.
|
||||
type AlertService_SubscribeAlertsServer = grpc.ServerStreamingServer[AlertMessage]
|
||||
|
||||
// AlertService_ServiceDesc is the grpc.ServiceDesc for AlertService service.
|
||||
// It's only intended for direct use with grpc.RegisterService,
|
||||
// and not to be introspected or modified (even as a copy)
|
||||
var AlertService_ServiceDesc = grpc.ServiceDesc{
|
||||
ServiceName: "AlertService",
|
||||
HandlerType: (*AlertServiceServer)(nil),
|
||||
Methods: []grpc.MethodDesc{},
|
||||
Streams: []grpc.StreamDesc{
|
||||
{
|
||||
StreamName: "SubscribeAlerts",
|
||||
Handler: _AlertService_SubscribeAlerts_Handler,
|
||||
ServerStreams: true,
|
||||
},
|
||||
},
|
||||
Metadata: "alerts.proto",
|
||||
}
|
||||
@@ -0,0 +1,311 @@
|
||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.36.11
|
||||
// protoc v3.21.12
|
||||
// source: common.proto
|
||||
|
||||
package protoapi
|
||||
|
||||
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)
|
||||
)
|
||||
|
||||
type DateTime struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
Value string `protobuf:"bytes,1,opt,name=Value,proto3" json:"Value,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *DateTime) Reset() {
|
||||
*x = DateTime{}
|
||||
mi := &file_common_proto_msgTypes[0]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
||||
func (x *DateTime) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*DateTime) ProtoMessage() {}
|
||||
|
||||
func (x *DateTime) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_common_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 DateTime.ProtoReflect.Descriptor instead.
|
||||
func (*DateTime) Descriptor() ([]byte, []int) {
|
||||
return file_common_proto_rawDescGZIP(), []int{0}
|
||||
}
|
||||
|
||||
func (x *DateTime) GetValue() string {
|
||||
if x != nil {
|
||||
return x.Value
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
type RequestDateTime struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
Value string `protobuf:"bytes,2,opt,name=Value,proto3" json:"Value,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *RequestDateTime) Reset() {
|
||||
*x = RequestDateTime{}
|
||||
mi := &file_common_proto_msgTypes[1]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
||||
func (x *RequestDateTime) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*RequestDateTime) ProtoMessage() {}
|
||||
|
||||
func (x *RequestDateTime) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_common_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 RequestDateTime.ProtoReflect.Descriptor instead.
|
||||
func (*RequestDateTime) Descriptor() ([]byte, []int) {
|
||||
return file_common_proto_rawDescGZIP(), []int{1}
|
||||
}
|
||||
|
||||
func (x *RequestDateTime) GetValue() string {
|
||||
if x != nil {
|
||||
return x.Value
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
type RequestPass struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
Seed int64 `protobuf:"varint,1,opt,name=Seed,proto3" json:"Seed,omitempty"`
|
||||
Length int64 `protobuf:"varint,8,opt,name=Length,proto3" json:"Length,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *RequestPass) Reset() {
|
||||
*x = RequestPass{}
|
||||
mi := &file_common_proto_msgTypes[2]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
||||
func (x *RequestPass) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*RequestPass) ProtoMessage() {}
|
||||
|
||||
func (x *RequestPass) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_common_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 RequestPass.ProtoReflect.Descriptor instead.
|
||||
func (*RequestPass) Descriptor() ([]byte, []int) {
|
||||
return file_common_proto_rawDescGZIP(), []int{2}
|
||||
}
|
||||
|
||||
func (x *RequestPass) GetSeed() int64 {
|
||||
if x != nil {
|
||||
return x.Seed
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (x *RequestPass) GetLength() int64 {
|
||||
if x != nil {
|
||||
return x.Length
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
type RandomPass struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
Password string `protobuf:"bytes,1,opt,name=Password,proto3" json:"Password,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *RandomPass) Reset() {
|
||||
*x = RandomPass{}
|
||||
mi := &file_common_proto_msgTypes[3]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
||||
func (x *RandomPass) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*RandomPass) ProtoMessage() {}
|
||||
|
||||
func (x *RandomPass) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_common_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 RandomPass.ProtoReflect.Descriptor instead.
|
||||
func (*RandomPass) Descriptor() ([]byte, []int) {
|
||||
return file_common_proto_rawDescGZIP(), []int{3}
|
||||
}
|
||||
|
||||
func (x *RandomPass) GetPassword() string {
|
||||
if x != nil {
|
||||
return x.Password
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
type EmptyRequest struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *EmptyRequest) Reset() {
|
||||
*x = EmptyRequest{}
|
||||
mi := &file_common_proto_msgTypes[4]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
||||
func (x *EmptyRequest) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*EmptyRequest) ProtoMessage() {}
|
||||
|
||||
func (x *EmptyRequest) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_common_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 EmptyRequest.ProtoReflect.Descriptor instead.
|
||||
func (*EmptyRequest) Descriptor() ([]byte, []int) {
|
||||
return file_common_proto_rawDescGZIP(), []int{4}
|
||||
}
|
||||
|
||||
var File_common_proto protoreflect.FileDescriptor
|
||||
|
||||
const file_common_proto_rawDesc = "" +
|
||||
"\n" +
|
||||
"\fcommon.proto\" \n" +
|
||||
"\bDateTime\x12\x14\n" +
|
||||
"\x05Value\x18\x01 \x01(\tR\x05Value\"'\n" +
|
||||
"\x0fRequestDateTime\x12\x14\n" +
|
||||
"\x05Value\x18\x02 \x01(\tR\x05Value\"9\n" +
|
||||
"\vRequestPass\x12\x12\n" +
|
||||
"\x04Seed\x18\x01 \x01(\x03R\x04Seed\x12\x16\n" +
|
||||
"\x06Length\x18\b \x01(\x03R\x06Length\"(\n" +
|
||||
"\n" +
|
||||
"RandomPass\x12\x1a\n" +
|
||||
"\bPassword\x18\x01 \x01(\tR\bPassword\"\x0e\n" +
|
||||
"\fEmptyRequestB\x16Z\x14./protoapi/;protoapib\x06proto3"
|
||||
|
||||
var (
|
||||
file_common_proto_rawDescOnce sync.Once
|
||||
file_common_proto_rawDescData []byte
|
||||
)
|
||||
|
||||
func file_common_proto_rawDescGZIP() []byte {
|
||||
file_common_proto_rawDescOnce.Do(func() {
|
||||
file_common_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_common_proto_rawDesc), len(file_common_proto_rawDesc)))
|
||||
})
|
||||
return file_common_proto_rawDescData
|
||||
}
|
||||
|
||||
var file_common_proto_msgTypes = make([]protoimpl.MessageInfo, 5)
|
||||
var file_common_proto_goTypes = []any{
|
||||
(*DateTime)(nil), // 0: DateTime
|
||||
(*RequestDateTime)(nil), // 1: RequestDateTime
|
||||
(*RequestPass)(nil), // 2: RequestPass
|
||||
(*RandomPass)(nil), // 3: RandomPass
|
||||
(*EmptyRequest)(nil), // 4: EmptyRequest
|
||||
}
|
||||
var file_common_proto_depIdxs = []int32{
|
||||
0, // [0:0] is the sub-list for method output_type
|
||||
0, // [0:0] is the sub-list for method input_type
|
||||
0, // [0:0] is the sub-list for extension type_name
|
||||
0, // [0:0] is the sub-list for extension extendee
|
||||
0, // [0:0] is the sub-list for field type_name
|
||||
}
|
||||
|
||||
func init() { file_common_proto_init() }
|
||||
func file_common_proto_init() {
|
||||
if File_common_proto != nil {
|
||||
return
|
||||
}
|
||||
type x struct{}
|
||||
out := protoimpl.TypeBuilder{
|
||||
File: protoimpl.DescBuilder{
|
||||
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
||||
RawDescriptor: unsafe.Slice(unsafe.StringData(file_common_proto_rawDesc), len(file_common_proto_rawDesc)),
|
||||
NumEnums: 0,
|
||||
NumMessages: 5,
|
||||
NumExtensions: 0,
|
||||
NumServices: 0,
|
||||
},
|
||||
GoTypes: file_common_proto_goTypes,
|
||||
DependencyIndexes: file_common_proto_depIdxs,
|
||||
MessageInfos: file_common_proto_msgTypes,
|
||||
}.Build()
|
||||
File_common_proto = out.File
|
||||
file_common_proto_goTypes = nil
|
||||
file_common_proto_depIdxs = nil
|
||||
}
|
||||
@@ -0,0 +1,212 @@
|
||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.36.11
|
||||
// protoc v3.21.12
|
||||
// source: core.proto
|
||||
|
||||
package protoapi
|
||||
|
||||
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)
|
||||
)
|
||||
|
||||
type SensingData struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
DeviceId string `protobuf:"bytes,1,opt,name=DeviceId,proto3" json:"DeviceId,omitempty"`
|
||||
Temperature float64 `protobuf:"fixed64,2,opt,name=Temperature,proto3" json:"Temperature,omitempty"`
|
||||
Humidity float64 `protobuf:"fixed64,3,opt,name=Humidity,proto3" json:"Humidity,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *SensingData) Reset() {
|
||||
*x = SensingData{}
|
||||
mi := &file_core_proto_msgTypes[0]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
||||
func (x *SensingData) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*SensingData) ProtoMessage() {}
|
||||
|
||||
func (x *SensingData) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_core_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 SensingData.ProtoReflect.Descriptor instead.
|
||||
func (*SensingData) Descriptor() ([]byte, []int) {
|
||||
return file_core_proto_rawDescGZIP(), []int{0}
|
||||
}
|
||||
|
||||
func (x *SensingData) GetDeviceId() string {
|
||||
if x != nil {
|
||||
return x.DeviceId
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *SensingData) GetTemperature() float64 {
|
||||
if x != nil {
|
||||
return x.Temperature
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (x *SensingData) GetHumidity() float64 {
|
||||
if x != nil {
|
||||
return x.Humidity
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
type SensingResponse struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
Success bool `protobuf:"varint,1,opt,name=Success,proto3" json:"Success,omitempty"`
|
||||
Message string `protobuf:"bytes,2,opt,name=Message,proto3" json:"Message,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *SensingResponse) Reset() {
|
||||
*x = SensingResponse{}
|
||||
mi := &file_core_proto_msgTypes[1]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
||||
func (x *SensingResponse) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*SensingResponse) ProtoMessage() {}
|
||||
|
||||
func (x *SensingResponse) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_core_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 SensingResponse.ProtoReflect.Descriptor instead.
|
||||
func (*SensingResponse) Descriptor() ([]byte, []int) {
|
||||
return file_core_proto_rawDescGZIP(), []int{1}
|
||||
}
|
||||
|
||||
func (x *SensingResponse) GetSuccess() bool {
|
||||
if x != nil {
|
||||
return x.Success
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (x *SensingResponse) GetMessage() string {
|
||||
if x != nil {
|
||||
return x.Message
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
var File_core_proto protoreflect.FileDescriptor
|
||||
|
||||
const file_core_proto_rawDesc = "" +
|
||||
"\n" +
|
||||
"\n" +
|
||||
"core.proto\x1a\fcommon.proto\"g\n" +
|
||||
"\vSensingData\x12\x1a\n" +
|
||||
"\bDeviceId\x18\x01 \x01(\tR\bDeviceId\x12 \n" +
|
||||
"\vTemperature\x18\x02 \x01(\x01R\vTemperature\x12\x1a\n" +
|
||||
"\bHumidity\x18\x03 \x01(\x01R\bHumidity\"E\n" +
|
||||
"\x0fSensingResponse\x12\x18\n" +
|
||||
"\aSuccess\x18\x01 \x01(\bR\aSuccess\x12\x18\n" +
|
||||
"\aMessage\x18\x02 \x01(\tR\aMessage2\x96\x01\n" +
|
||||
"\vCoreService\x12&\n" +
|
||||
"\aGetDate\x12\x10.RequestDateTime\x1a\t.DateTime\x123\n" +
|
||||
"\x11UpdateSensingData\x12\f.SensingData\x1a\x10.SensingResponse\x12*\n" +
|
||||
"\rGetRandomPass\x12\f.RequestPass\x1a\v.RandomPassB\x16Z\x14./protoapi/;protoapib\x06proto3"
|
||||
|
||||
var (
|
||||
file_core_proto_rawDescOnce sync.Once
|
||||
file_core_proto_rawDescData []byte
|
||||
)
|
||||
|
||||
func file_core_proto_rawDescGZIP() []byte {
|
||||
file_core_proto_rawDescOnce.Do(func() {
|
||||
file_core_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_core_proto_rawDesc), len(file_core_proto_rawDesc)))
|
||||
})
|
||||
return file_core_proto_rawDescData
|
||||
}
|
||||
|
||||
var file_core_proto_msgTypes = make([]protoimpl.MessageInfo, 2)
|
||||
var file_core_proto_goTypes = []any{
|
||||
(*SensingData)(nil), // 0: SensingData
|
||||
(*SensingResponse)(nil), // 1: SensingResponse
|
||||
(*RequestDateTime)(nil), // 2: RequestDateTime
|
||||
(*RequestPass)(nil), // 3: RequestPass
|
||||
(*DateTime)(nil), // 4: DateTime
|
||||
(*RandomPass)(nil), // 5: RandomPass
|
||||
}
|
||||
var file_core_proto_depIdxs = []int32{
|
||||
2, // 0: CoreService.GetDate:input_type -> RequestDateTime
|
||||
0, // 1: CoreService.UpdateSensingData:input_type -> SensingData
|
||||
3, // 2: CoreService.GetRandomPass:input_type -> RequestPass
|
||||
4, // 3: CoreService.GetDate:output_type -> DateTime
|
||||
1, // 4: CoreService.UpdateSensingData:output_type -> SensingResponse
|
||||
5, // 5: CoreService.GetRandomPass:output_type -> RandomPass
|
||||
3, // [3:6] is the sub-list for method output_type
|
||||
0, // [0:3] is the sub-list for method input_type
|
||||
0, // [0:0] is the sub-list for extension type_name
|
||||
0, // [0:0] is the sub-list for extension extendee
|
||||
0, // [0:0] is the sub-list for field type_name
|
||||
}
|
||||
|
||||
func init() { file_core_proto_init() }
|
||||
func file_core_proto_init() {
|
||||
if File_core_proto != nil {
|
||||
return
|
||||
}
|
||||
file_common_proto_init()
|
||||
type x struct{}
|
||||
out := protoimpl.TypeBuilder{
|
||||
File: protoimpl.DescBuilder{
|
||||
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
||||
RawDescriptor: unsafe.Slice(unsafe.StringData(file_core_proto_rawDesc), len(file_core_proto_rawDesc)),
|
||||
NumEnums: 0,
|
||||
NumMessages: 2,
|
||||
NumExtensions: 0,
|
||||
NumServices: 1,
|
||||
},
|
||||
GoTypes: file_core_proto_goTypes,
|
||||
DependencyIndexes: file_core_proto_depIdxs,
|
||||
MessageInfos: file_core_proto_msgTypes,
|
||||
}.Build()
|
||||
File_core_proto = out.File
|
||||
file_core_proto_goTypes = nil
|
||||
file_core_proto_depIdxs = nil
|
||||
}
|
||||
@@ -0,0 +1,197 @@
|
||||
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
|
||||
// versions:
|
||||
// - protoc-gen-go-grpc v1.6.2
|
||||
// - protoc v3.21.12
|
||||
// source: core.proto
|
||||
|
||||
package protoapi
|
||||
|
||||
import (
|
||||
context "context"
|
||||
grpc "google.golang.org/grpc"
|
||||
codes "google.golang.org/grpc/codes"
|
||||
status "google.golang.org/grpc/status"
|
||||
)
|
||||
|
||||
// This is a compile-time assertion to ensure that this generated file
|
||||
// is compatible with the grpc package it is being compiled against.
|
||||
// Requires gRPC-Go v1.64.0 or later.
|
||||
const _ = grpc.SupportPackageIsVersion9
|
||||
|
||||
const (
|
||||
CoreService_GetDate_FullMethodName = "/CoreService/GetDate"
|
||||
CoreService_UpdateSensingData_FullMethodName = "/CoreService/UpdateSensingData"
|
||||
CoreService_GetRandomPass_FullMethodName = "/CoreService/GetRandomPass"
|
||||
)
|
||||
|
||||
// CoreServiceClient is the client API for CoreService service.
|
||||
//
|
||||
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream.
|
||||
type CoreServiceClient interface {
|
||||
GetDate(ctx context.Context, in *RequestDateTime, opts ...grpc.CallOption) (*DateTime, error)
|
||||
UpdateSensingData(ctx context.Context, in *SensingData, opts ...grpc.CallOption) (*SensingResponse, error)
|
||||
GetRandomPass(ctx context.Context, in *RequestPass, opts ...grpc.CallOption) (*RandomPass, error)
|
||||
}
|
||||
|
||||
type coreServiceClient struct {
|
||||
cc grpc.ClientConnInterface
|
||||
}
|
||||
|
||||
func NewCoreServiceClient(cc grpc.ClientConnInterface) CoreServiceClient {
|
||||
return &coreServiceClient{cc}
|
||||
}
|
||||
|
||||
func (c *coreServiceClient) GetDate(ctx context.Context, in *RequestDateTime, opts ...grpc.CallOption) (*DateTime, error) {
|
||||
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
||||
out := new(DateTime)
|
||||
err := c.cc.Invoke(ctx, CoreService_GetDate_FullMethodName, in, out, cOpts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *coreServiceClient) UpdateSensingData(ctx context.Context, in *SensingData, opts ...grpc.CallOption) (*SensingResponse, error) {
|
||||
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
||||
out := new(SensingResponse)
|
||||
err := c.cc.Invoke(ctx, CoreService_UpdateSensingData_FullMethodName, in, out, cOpts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *coreServiceClient) GetRandomPass(ctx context.Context, in *RequestPass, opts ...grpc.CallOption) (*RandomPass, error) {
|
||||
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
||||
out := new(RandomPass)
|
||||
err := c.cc.Invoke(ctx, CoreService_GetRandomPass_FullMethodName, in, out, cOpts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// CoreServiceServer is the server API for CoreService service.
|
||||
// All implementations must embed UnimplementedCoreServiceServer
|
||||
// for forward compatibility.
|
||||
type CoreServiceServer interface {
|
||||
GetDate(context.Context, *RequestDateTime) (*DateTime, error)
|
||||
UpdateSensingData(context.Context, *SensingData) (*SensingResponse, error)
|
||||
GetRandomPass(context.Context, *RequestPass) (*RandomPass, error)
|
||||
mustEmbedUnimplementedCoreServiceServer()
|
||||
}
|
||||
|
||||
// UnimplementedCoreServiceServer must be embedded to have
|
||||
// forward compatible implementations.
|
||||
//
|
||||
// NOTE: this should be embedded by value instead of pointer to avoid a nil
|
||||
// pointer dereference when methods are called.
|
||||
type UnimplementedCoreServiceServer struct{}
|
||||
|
||||
func (UnimplementedCoreServiceServer) GetDate(context.Context, *RequestDateTime) (*DateTime, error) {
|
||||
return nil, status.Error(codes.Unimplemented, "method GetDate not implemented")
|
||||
}
|
||||
func (UnimplementedCoreServiceServer) UpdateSensingData(context.Context, *SensingData) (*SensingResponse, error) {
|
||||
return nil, status.Error(codes.Unimplemented, "method UpdateSensingData not implemented")
|
||||
}
|
||||
func (UnimplementedCoreServiceServer) GetRandomPass(context.Context, *RequestPass) (*RandomPass, error) {
|
||||
return nil, status.Error(codes.Unimplemented, "method GetRandomPass not implemented")
|
||||
}
|
||||
func (UnimplementedCoreServiceServer) mustEmbedUnimplementedCoreServiceServer() {}
|
||||
func (UnimplementedCoreServiceServer) testEmbeddedByValue() {}
|
||||
|
||||
// UnsafeCoreServiceServer may be embedded to opt out of forward compatibility for this service.
|
||||
// Use of this interface is not recommended, as added methods to CoreServiceServer will
|
||||
// result in compilation errors.
|
||||
type UnsafeCoreServiceServer interface {
|
||||
mustEmbedUnimplementedCoreServiceServer()
|
||||
}
|
||||
|
||||
func RegisterCoreServiceServer(s grpc.ServiceRegistrar, srv CoreServiceServer) {
|
||||
// If the following call panics, it indicates UnimplementedCoreServiceServer was
|
||||
// embedded by pointer and is nil. This will cause panics if an
|
||||
// unimplemented method is ever invoked, so we test this at initialization
|
||||
// time to prevent it from happening at runtime later due to I/O.
|
||||
if t, ok := srv.(interface{ testEmbeddedByValue() }); ok {
|
||||
t.testEmbeddedByValue()
|
||||
}
|
||||
s.RegisterService(&CoreService_ServiceDesc, srv)
|
||||
}
|
||||
|
||||
func _CoreService_GetDate_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(RequestDateTime)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(CoreServiceServer).GetDate(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: CoreService_GetDate_FullMethodName,
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(CoreServiceServer).GetDate(ctx, req.(*RequestDateTime))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _CoreService_UpdateSensingData_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(SensingData)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(CoreServiceServer).UpdateSensingData(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: CoreService_UpdateSensingData_FullMethodName,
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(CoreServiceServer).UpdateSensingData(ctx, req.(*SensingData))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _CoreService_GetRandomPass_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(RequestPass)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(CoreServiceServer).GetRandomPass(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: CoreService_GetRandomPass_FullMethodName,
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(CoreServiceServer).GetRandomPass(ctx, req.(*RequestPass))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
// CoreService_ServiceDesc is the grpc.ServiceDesc for CoreService service.
|
||||
// It's only intended for direct use with grpc.RegisterService,
|
||||
// and not to be introspected or modified (even as a copy)
|
||||
var CoreService_ServiceDesc = grpc.ServiceDesc{
|
||||
ServiceName: "CoreService",
|
||||
HandlerType: (*CoreServiceServer)(nil),
|
||||
Methods: []grpc.MethodDesc{
|
||||
{
|
||||
MethodName: "GetDate",
|
||||
Handler: _CoreService_GetDate_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "UpdateSensingData",
|
||||
Handler: _CoreService_UpdateSensingData_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "GetRandomPass",
|
||||
Handler: _CoreService_GetRandomPass_Handler,
|
||||
},
|
||||
},
|
||||
Streams: []grpc.StreamDesc{},
|
||||
Metadata: "core.proto",
|
||||
}
|
||||
@@ -0,0 +1,373 @@
|
||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.36.11
|
||||
// protoc v3.21.12
|
||||
// source: filetransfer.proto
|
||||
|
||||
package protoapi
|
||||
|
||||
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)
|
||||
)
|
||||
|
||||
type FileChunk struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
FileName string `protobuf:"bytes,1,opt,name=FileName,proto3" json:"FileName,omitempty"`
|
||||
Content []byte `protobuf:"bytes,2,opt,name=Content,proto3" json:"Content,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *FileChunk) Reset() {
|
||||
*x = FileChunk{}
|
||||
mi := &file_filetransfer_proto_msgTypes[0]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
||||
func (x *FileChunk) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*FileChunk) ProtoMessage() {}
|
||||
|
||||
func (x *FileChunk) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_filetransfer_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 FileChunk.ProtoReflect.Descriptor instead.
|
||||
func (*FileChunk) Descriptor() ([]byte, []int) {
|
||||
return file_filetransfer_proto_rawDescGZIP(), []int{0}
|
||||
}
|
||||
|
||||
func (x *FileChunk) GetFileName() string {
|
||||
if x != nil {
|
||||
return x.FileName
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *FileChunk) GetContent() []byte {
|
||||
if x != nil {
|
||||
return x.Content
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type UploadStatus struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
Success bool `protobuf:"varint,1,opt,name=Success,proto3" json:"Success,omitempty"`
|
||||
Message string `protobuf:"bytes,2,opt,name=Message,proto3" json:"Message,omitempty"`
|
||||
BytesUploaded int64 `protobuf:"varint,3,opt,name=BytesUploaded,proto3" json:"BytesUploaded,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *UploadStatus) Reset() {
|
||||
*x = UploadStatus{}
|
||||
mi := &file_filetransfer_proto_msgTypes[1]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
||||
func (x *UploadStatus) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*UploadStatus) ProtoMessage() {}
|
||||
|
||||
func (x *UploadStatus) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_filetransfer_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 UploadStatus.ProtoReflect.Descriptor instead.
|
||||
func (*UploadStatus) Descriptor() ([]byte, []int) {
|
||||
return file_filetransfer_proto_rawDescGZIP(), []int{1}
|
||||
}
|
||||
|
||||
func (x *UploadStatus) GetSuccess() bool {
|
||||
if x != nil {
|
||||
return x.Success
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (x *UploadStatus) GetMessage() string {
|
||||
if x != nil {
|
||||
return x.Message
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *UploadStatus) GetBytesUploaded() int64 {
|
||||
if x != nil {
|
||||
return x.BytesUploaded
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
type FileMetadata struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
FileName string `protobuf:"bytes,1,opt,name=FileName,proto3" json:"FileName,omitempty"`
|
||||
FileSize int64 `protobuf:"varint,2,opt,name=FileSize,proto3" json:"FileSize,omitempty"`
|
||||
UploadedAt int64 `protobuf:"varint,3,opt,name=UploadedAt,proto3" json:"UploadedAt,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *FileMetadata) Reset() {
|
||||
*x = FileMetadata{}
|
||||
mi := &file_filetransfer_proto_msgTypes[2]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
||||
func (x *FileMetadata) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*FileMetadata) ProtoMessage() {}
|
||||
|
||||
func (x *FileMetadata) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_filetransfer_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 FileMetadata.ProtoReflect.Descriptor instead.
|
||||
func (*FileMetadata) Descriptor() ([]byte, []int) {
|
||||
return file_filetransfer_proto_rawDescGZIP(), []int{2}
|
||||
}
|
||||
|
||||
func (x *FileMetadata) GetFileName() string {
|
||||
if x != nil {
|
||||
return x.FileName
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *FileMetadata) GetFileSize() int64 {
|
||||
if x != nil {
|
||||
return x.FileSize
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (x *FileMetadata) GetUploadedAt() int64 {
|
||||
if x != nil {
|
||||
return x.UploadedAt
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
type FileList struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
Files []*FileMetadata `protobuf:"bytes,1,rep,name=Files,proto3" json:"Files,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *FileList) Reset() {
|
||||
*x = FileList{}
|
||||
mi := &file_filetransfer_proto_msgTypes[3]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
||||
func (x *FileList) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*FileList) ProtoMessage() {}
|
||||
|
||||
func (x *FileList) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_filetransfer_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 FileList.ProtoReflect.Descriptor instead.
|
||||
func (*FileList) Descriptor() ([]byte, []int) {
|
||||
return file_filetransfer_proto_rawDescGZIP(), []int{3}
|
||||
}
|
||||
|
||||
func (x *FileList) GetFiles() []*FileMetadata {
|
||||
if x != nil {
|
||||
return x.Files
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type DownloadRequest struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
FileName string `protobuf:"bytes,1,opt,name=FileName,proto3" json:"FileName,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *DownloadRequest) Reset() {
|
||||
*x = DownloadRequest{}
|
||||
mi := &file_filetransfer_proto_msgTypes[4]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
||||
func (x *DownloadRequest) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*DownloadRequest) ProtoMessage() {}
|
||||
|
||||
func (x *DownloadRequest) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_filetransfer_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 DownloadRequest.ProtoReflect.Descriptor instead.
|
||||
func (*DownloadRequest) Descriptor() ([]byte, []int) {
|
||||
return file_filetransfer_proto_rawDescGZIP(), []int{4}
|
||||
}
|
||||
|
||||
func (x *DownloadRequest) GetFileName() string {
|
||||
if x != nil {
|
||||
return x.FileName
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
var File_filetransfer_proto protoreflect.FileDescriptor
|
||||
|
||||
const file_filetransfer_proto_rawDesc = "" +
|
||||
"\n" +
|
||||
"\x12filetransfer.proto\x1a\fcommon.proto\"A\n" +
|
||||
"\tFileChunk\x12\x1a\n" +
|
||||
"\bFileName\x18\x01 \x01(\tR\bFileName\x12\x18\n" +
|
||||
"\aContent\x18\x02 \x01(\fR\aContent\"h\n" +
|
||||
"\fUploadStatus\x12\x18\n" +
|
||||
"\aSuccess\x18\x01 \x01(\bR\aSuccess\x12\x18\n" +
|
||||
"\aMessage\x18\x02 \x01(\tR\aMessage\x12$\n" +
|
||||
"\rBytesUploaded\x18\x03 \x01(\x03R\rBytesUploaded\"f\n" +
|
||||
"\fFileMetadata\x12\x1a\n" +
|
||||
"\bFileName\x18\x01 \x01(\tR\bFileName\x12\x1a\n" +
|
||||
"\bFileSize\x18\x02 \x01(\x03R\bFileSize\x12\x1e\n" +
|
||||
"\n" +
|
||||
"UploadedAt\x18\x03 \x01(\x03R\n" +
|
||||
"UploadedAt\"/\n" +
|
||||
"\bFileList\x12#\n" +
|
||||
"\x05Files\x18\x01 \x03(\v2\r.FileMetadataR\x05Files\"-\n" +
|
||||
"\x0fDownloadRequest\x12\x1a\n" +
|
||||
"\bFileName\x18\x01 \x01(\tR\bFileName2\x97\x01\n" +
|
||||
"\x13FileTransferService\x12)\n" +
|
||||
"\n" +
|
||||
"UploadFile\x12\n" +
|
||||
".FileChunk\x1a\r.UploadStatus(\x01\x12%\n" +
|
||||
"\tListFiles\x12\r.EmptyRequest\x1a\t.FileList\x12.\n" +
|
||||
"\fDownloadFile\x12\x10.DownloadRequest\x1a\n" +
|
||||
".FileChunk0\x01B\x16Z\x14./protoapi/;protoapib\x06proto3"
|
||||
|
||||
var (
|
||||
file_filetransfer_proto_rawDescOnce sync.Once
|
||||
file_filetransfer_proto_rawDescData []byte
|
||||
)
|
||||
|
||||
func file_filetransfer_proto_rawDescGZIP() []byte {
|
||||
file_filetransfer_proto_rawDescOnce.Do(func() {
|
||||
file_filetransfer_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_filetransfer_proto_rawDesc), len(file_filetransfer_proto_rawDesc)))
|
||||
})
|
||||
return file_filetransfer_proto_rawDescData
|
||||
}
|
||||
|
||||
var file_filetransfer_proto_msgTypes = make([]protoimpl.MessageInfo, 5)
|
||||
var file_filetransfer_proto_goTypes = []any{
|
||||
(*FileChunk)(nil), // 0: FileChunk
|
||||
(*UploadStatus)(nil), // 1: UploadStatus
|
||||
(*FileMetadata)(nil), // 2: FileMetadata
|
||||
(*FileList)(nil), // 3: FileList
|
||||
(*DownloadRequest)(nil), // 4: DownloadRequest
|
||||
(*EmptyRequest)(nil), // 5: EmptyRequest
|
||||
}
|
||||
var file_filetransfer_proto_depIdxs = []int32{
|
||||
2, // 0: FileList.Files:type_name -> FileMetadata
|
||||
0, // 1: FileTransferService.UploadFile:input_type -> FileChunk
|
||||
5, // 2: FileTransferService.ListFiles:input_type -> EmptyRequest
|
||||
4, // 3: FileTransferService.DownloadFile:input_type -> DownloadRequest
|
||||
1, // 4: FileTransferService.UploadFile:output_type -> UploadStatus
|
||||
3, // 5: FileTransferService.ListFiles:output_type -> FileList
|
||||
0, // 6: FileTransferService.DownloadFile:output_type -> FileChunk
|
||||
4, // [4:7] is the sub-list for method output_type
|
||||
1, // [1:4] is the sub-list for method input_type
|
||||
1, // [1:1] is the sub-list for extension type_name
|
||||
1, // [1:1] is the sub-list for extension extendee
|
||||
0, // [0:1] is the sub-list for field type_name
|
||||
}
|
||||
|
||||
func init() { file_filetransfer_proto_init() }
|
||||
func file_filetransfer_proto_init() {
|
||||
if File_filetransfer_proto != nil {
|
||||
return
|
||||
}
|
||||
file_common_proto_init()
|
||||
type x struct{}
|
||||
out := protoimpl.TypeBuilder{
|
||||
File: protoimpl.DescBuilder{
|
||||
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
||||
RawDescriptor: unsafe.Slice(unsafe.StringData(file_filetransfer_proto_rawDesc), len(file_filetransfer_proto_rawDesc)),
|
||||
NumEnums: 0,
|
||||
NumMessages: 5,
|
||||
NumExtensions: 0,
|
||||
NumServices: 1,
|
||||
},
|
||||
GoTypes: file_filetransfer_proto_goTypes,
|
||||
DependencyIndexes: file_filetransfer_proto_depIdxs,
|
||||
MessageInfos: file_filetransfer_proto_msgTypes,
|
||||
}.Build()
|
||||
File_filetransfer_proto = out.File
|
||||
file_filetransfer_proto_goTypes = nil
|
||||
file_filetransfer_proto_depIdxs = nil
|
||||
}
|
||||
@@ -0,0 +1,194 @@
|
||||
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
|
||||
// versions:
|
||||
// - protoc-gen-go-grpc v1.6.2
|
||||
// - protoc v3.21.12
|
||||
// source: filetransfer.proto
|
||||
|
||||
package protoapi
|
||||
|
||||
import (
|
||||
context "context"
|
||||
grpc "google.golang.org/grpc"
|
||||
codes "google.golang.org/grpc/codes"
|
||||
status "google.golang.org/grpc/status"
|
||||
)
|
||||
|
||||
// This is a compile-time assertion to ensure that this generated file
|
||||
// is compatible with the grpc package it is being compiled against.
|
||||
// Requires gRPC-Go v1.64.0 or later.
|
||||
const _ = grpc.SupportPackageIsVersion9
|
||||
|
||||
const (
|
||||
FileTransferService_UploadFile_FullMethodName = "/FileTransferService/UploadFile"
|
||||
FileTransferService_ListFiles_FullMethodName = "/FileTransferService/ListFiles"
|
||||
FileTransferService_DownloadFile_FullMethodName = "/FileTransferService/DownloadFile"
|
||||
)
|
||||
|
||||
// FileTransferServiceClient is the client API for FileTransferService service.
|
||||
//
|
||||
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream.
|
||||
type FileTransferServiceClient interface {
|
||||
UploadFile(ctx context.Context, opts ...grpc.CallOption) (grpc.ClientStreamingClient[FileChunk, UploadStatus], error)
|
||||
ListFiles(ctx context.Context, in *EmptyRequest, opts ...grpc.CallOption) (*FileList, error)
|
||||
DownloadFile(ctx context.Context, in *DownloadRequest, opts ...grpc.CallOption) (grpc.ServerStreamingClient[FileChunk], error)
|
||||
}
|
||||
|
||||
type fileTransferServiceClient struct {
|
||||
cc grpc.ClientConnInterface
|
||||
}
|
||||
|
||||
func NewFileTransferServiceClient(cc grpc.ClientConnInterface) FileTransferServiceClient {
|
||||
return &fileTransferServiceClient{cc}
|
||||
}
|
||||
|
||||
func (c *fileTransferServiceClient) UploadFile(ctx context.Context, opts ...grpc.CallOption) (grpc.ClientStreamingClient[FileChunk, UploadStatus], error) {
|
||||
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
||||
stream, err := c.cc.NewStream(ctx, &FileTransferService_ServiceDesc.Streams[0], FileTransferService_UploadFile_FullMethodName, cOpts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
x := &grpc.GenericClientStream[FileChunk, UploadStatus]{ClientStream: stream}
|
||||
return x, nil
|
||||
}
|
||||
|
||||
// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name.
|
||||
type FileTransferService_UploadFileClient = grpc.ClientStreamingClient[FileChunk, UploadStatus]
|
||||
|
||||
func (c *fileTransferServiceClient) ListFiles(ctx context.Context, in *EmptyRequest, opts ...grpc.CallOption) (*FileList, error) {
|
||||
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
||||
out := new(FileList)
|
||||
err := c.cc.Invoke(ctx, FileTransferService_ListFiles_FullMethodName, in, out, cOpts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *fileTransferServiceClient) DownloadFile(ctx context.Context, in *DownloadRequest, opts ...grpc.CallOption) (grpc.ServerStreamingClient[FileChunk], error) {
|
||||
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
||||
stream, err := c.cc.NewStream(ctx, &FileTransferService_ServiceDesc.Streams[1], FileTransferService_DownloadFile_FullMethodName, cOpts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
x := &grpc.GenericClientStream[DownloadRequest, FileChunk]{ClientStream: stream}
|
||||
if err := x.ClientStream.SendMsg(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := x.ClientStream.CloseSend(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return x, nil
|
||||
}
|
||||
|
||||
// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name.
|
||||
type FileTransferService_DownloadFileClient = grpc.ServerStreamingClient[FileChunk]
|
||||
|
||||
// FileTransferServiceServer is the server API for FileTransferService service.
|
||||
// All implementations must embed UnimplementedFileTransferServiceServer
|
||||
// for forward compatibility.
|
||||
type FileTransferServiceServer interface {
|
||||
UploadFile(grpc.ClientStreamingServer[FileChunk, UploadStatus]) error
|
||||
ListFiles(context.Context, *EmptyRequest) (*FileList, error)
|
||||
DownloadFile(*DownloadRequest, grpc.ServerStreamingServer[FileChunk]) error
|
||||
mustEmbedUnimplementedFileTransferServiceServer()
|
||||
}
|
||||
|
||||
// UnimplementedFileTransferServiceServer must be embedded to have
|
||||
// forward compatible implementations.
|
||||
//
|
||||
// NOTE: this should be embedded by value instead of pointer to avoid a nil
|
||||
// pointer dereference when methods are called.
|
||||
type UnimplementedFileTransferServiceServer struct{}
|
||||
|
||||
func (UnimplementedFileTransferServiceServer) UploadFile(grpc.ClientStreamingServer[FileChunk, UploadStatus]) error {
|
||||
return status.Error(codes.Unimplemented, "method UploadFile not implemented")
|
||||
}
|
||||
func (UnimplementedFileTransferServiceServer) ListFiles(context.Context, *EmptyRequest) (*FileList, error) {
|
||||
return nil, status.Error(codes.Unimplemented, "method ListFiles not implemented")
|
||||
}
|
||||
func (UnimplementedFileTransferServiceServer) DownloadFile(*DownloadRequest, grpc.ServerStreamingServer[FileChunk]) error {
|
||||
return status.Error(codes.Unimplemented, "method DownloadFile not implemented")
|
||||
}
|
||||
func (UnimplementedFileTransferServiceServer) mustEmbedUnimplementedFileTransferServiceServer() {}
|
||||
func (UnimplementedFileTransferServiceServer) testEmbeddedByValue() {}
|
||||
|
||||
// UnsafeFileTransferServiceServer may be embedded to opt out of forward compatibility for this service.
|
||||
// Use of this interface is not recommended, as added methods to FileTransferServiceServer will
|
||||
// result in compilation errors.
|
||||
type UnsafeFileTransferServiceServer interface {
|
||||
mustEmbedUnimplementedFileTransferServiceServer()
|
||||
}
|
||||
|
||||
func RegisterFileTransferServiceServer(s grpc.ServiceRegistrar, srv FileTransferServiceServer) {
|
||||
// If the following call panics, it indicates UnimplementedFileTransferServiceServer was
|
||||
// embedded by pointer and is nil. This will cause panics if an
|
||||
// unimplemented method is ever invoked, so we test this at initialization
|
||||
// time to prevent it from happening at runtime later due to I/O.
|
||||
if t, ok := srv.(interface{ testEmbeddedByValue() }); ok {
|
||||
t.testEmbeddedByValue()
|
||||
}
|
||||
s.RegisterService(&FileTransferService_ServiceDesc, srv)
|
||||
}
|
||||
|
||||
func _FileTransferService_UploadFile_Handler(srv interface{}, stream grpc.ServerStream) error {
|
||||
return srv.(FileTransferServiceServer).UploadFile(&grpc.GenericServerStream[FileChunk, UploadStatus]{ServerStream: stream})
|
||||
}
|
||||
|
||||
// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name.
|
||||
type FileTransferService_UploadFileServer = grpc.ClientStreamingServer[FileChunk, UploadStatus]
|
||||
|
||||
func _FileTransferService_ListFiles_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(EmptyRequest)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(FileTransferServiceServer).ListFiles(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: FileTransferService_ListFiles_FullMethodName,
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(FileTransferServiceServer).ListFiles(ctx, req.(*EmptyRequest))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _FileTransferService_DownloadFile_Handler(srv interface{}, stream grpc.ServerStream) error {
|
||||
m := new(DownloadRequest)
|
||||
if err := stream.RecvMsg(m); err != nil {
|
||||
return err
|
||||
}
|
||||
return srv.(FileTransferServiceServer).DownloadFile(m, &grpc.GenericServerStream[DownloadRequest, FileChunk]{ServerStream: stream})
|
||||
}
|
||||
|
||||
// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name.
|
||||
type FileTransferService_DownloadFileServer = grpc.ServerStreamingServer[FileChunk]
|
||||
|
||||
// FileTransferService_ServiceDesc is the grpc.ServiceDesc for FileTransferService service.
|
||||
// It's only intended for direct use with grpc.RegisterService,
|
||||
// and not to be introspected or modified (even as a copy)
|
||||
var FileTransferService_ServiceDesc = grpc.ServiceDesc{
|
||||
ServiceName: "FileTransferService",
|
||||
HandlerType: (*FileTransferServiceServer)(nil),
|
||||
Methods: []grpc.MethodDesc{
|
||||
{
|
||||
MethodName: "ListFiles",
|
||||
Handler: _FileTransferService_ListFiles_Handler,
|
||||
},
|
||||
},
|
||||
Streams: []grpc.StreamDesc{
|
||||
{
|
||||
StreamName: "UploadFile",
|
||||
Handler: _FileTransferService_UploadFile_Handler,
|
||||
ClientStreams: true,
|
||||
},
|
||||
{
|
||||
StreamName: "DownloadFile",
|
||||
Handler: _FileTransferService_DownloadFile_Handler,
|
||||
ServerStreams: true,
|
||||
},
|
||||
},
|
||||
Metadata: "filetransfer.proto",
|
||||
}
|
||||
@@ -0,0 +1,257 @@
|
||||
package modular
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"math/rand"
|
||||
"net"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"grpccanary/lib/grpc/modular/protoapi"
|
||||
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/reflection"
|
||||
"google.golang.org/grpc/status"
|
||||
)
|
||||
|
||||
type UploadedFile struct {
|
||||
FileName string
|
||||
Content []byte
|
||||
UploadedAt int64
|
||||
}
|
||||
|
||||
type AlertSubscriber struct {
|
||||
ClientId string
|
||||
Channel chan *protoapi.AlertMessage
|
||||
}
|
||||
|
||||
var (
|
||||
fileStore = make(map[string]*UploadedFile)
|
||||
storeMu sync.RWMutex
|
||||
subscribers = make(map[string]*AlertSubscriber)
|
||||
subMu sync.Mutex
|
||||
)
|
||||
|
||||
func publishAlert(alert *protoapi.AlertMessage) {
|
||||
subMu.Lock()
|
||||
defer subMu.Unlock()
|
||||
for _, sub := range subscribers {
|
||||
select {
|
||||
case sub.Channel <- alert:
|
||||
default:
|
||||
fmt.Printf("Alert channel blocked for client %s, dropping event\n", sub.ClientId)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func random(min, max int, src rand.Source) int {
|
||||
return rand.New(src).Intn(max-min) + min
|
||||
}
|
||||
|
||||
func getString(len int64, src rand.Source) string {
|
||||
temp := ""
|
||||
startChar := "!"
|
||||
var i int64 = 1
|
||||
for {
|
||||
myRand := random(0, 94, src)
|
||||
newChar := string(startChar[0] + byte(myRand))
|
||||
temp = temp + newChar
|
||||
if i == len {
|
||||
break
|
||||
}
|
||||
i++
|
||||
}
|
||||
return temp
|
||||
}
|
||||
|
||||
// 1. Core Server Implementation
|
||||
type CoreServer struct {
|
||||
protoapi.UnimplementedCoreServiceServer
|
||||
}
|
||||
|
||||
func (CoreServer) GetDate(ctx context.Context, r *protoapi.RequestDateTime) (*protoapi.DateTime, error) {
|
||||
currentTime := time.Now()
|
||||
return &protoapi.DateTime{
|
||||
Value: currentTime.String(),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (CoreServer) UpdateSensingData(ctx context.Context, r *protoapi.SensingData) (*protoapi.SensingResponse, error) {
|
||||
fmt.Printf("[Modular Core] Received sensing data - Device: %s, Temp: %.2f°C, Humid: %.2f%%\n", r.GetDeviceId(), r.GetTemperature(), r.GetHumidity())
|
||||
|
||||
if r.GetTemperature() > 40.0 {
|
||||
fmt.Printf("⚠️ [Modular Core] Critical temperature detected: %.2f°C! Publishing warning...\n", r.GetTemperature())
|
||||
publishAlert(&protoapi.AlertMessage{
|
||||
AlertId: fmt.Sprintf("alert-%d", time.Now().UnixNano()),
|
||||
DeviceId: r.GetDeviceId(),
|
||||
Message: fmt.Sprintf("Critical high temperature: %.2f°C (Humidity: %.2f%%)", r.GetTemperature(), r.GetHumidity()),
|
||||
Timestamp: time.Now().Unix(),
|
||||
})
|
||||
}
|
||||
|
||||
return &protoapi.SensingResponse{
|
||||
Success: true,
|
||||
Message: fmt.Sprintf("Sensing data updated successfully for device %s", r.GetDeviceId()),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (CoreServer) GetRandomPass(ctx context.Context, r *protoapi.RequestPass) (*protoapi.RandomPass, error) {
|
||||
src := rand.NewSource(r.GetSeed())
|
||||
temp := getString(r.GetLength(), src)
|
||||
return &protoapi.RandomPass{
|
||||
Password: temp,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// 2. File Transfer Server Implementation
|
||||
type FileTransferServer struct {
|
||||
protoapi.UnimplementedFileTransferServiceServer
|
||||
}
|
||||
|
||||
func (FileTransferServer) UploadFile(stream protoapi.FileTransferService_UploadFileServer) error {
|
||||
var totalBytes int64
|
||||
var fileName string
|
||||
var buffer []byte
|
||||
|
||||
for {
|
||||
chunk, err := stream.Recv()
|
||||
if err == io.EOF {
|
||||
fmt.Printf("[Modular File] File upload completed. Received %d bytes for file '%s'\n", totalBytes, fileName)
|
||||
|
||||
if fileName != "" {
|
||||
storeMu.Lock()
|
||||
fileStore[fileName] = &UploadedFile{
|
||||
FileName: fileName,
|
||||
Content: buffer,
|
||||
UploadedAt: time.Now().Unix(),
|
||||
}
|
||||
storeMu.Unlock()
|
||||
}
|
||||
|
||||
return stream.SendAndClose(&protoapi.UploadStatus{
|
||||
Success: true,
|
||||
Message: fmt.Sprintf("File '%s' uploaded successfully.", fileName),
|
||||
BytesUploaded: totalBytes,
|
||||
})
|
||||
}
|
||||
if err != nil {
|
||||
fmt.Println("[Modular File] File upload error:", err)
|
||||
return err
|
||||
}
|
||||
|
||||
if fileName == "" {
|
||||
fileName = chunk.GetFileName()
|
||||
}
|
||||
buffer = append(buffer, chunk.GetContent()...)
|
||||
totalBytes += int64(len(chunk.GetContent()))
|
||||
}
|
||||
}
|
||||
|
||||
func (FileTransferServer) ListFiles(ctx context.Context, r *protoapi.EmptyRequest) (*protoapi.FileList, error) {
|
||||
storeMu.RLock()
|
||||
defer storeMu.RUnlock()
|
||||
|
||||
var files []*protoapi.FileMetadata
|
||||
for _, f := range fileStore {
|
||||
files = append(files, &protoapi.FileMetadata{
|
||||
FileName: f.FileName,
|
||||
FileSize: int64(len(f.Content)),
|
||||
UploadedAt: f.UploadedAt,
|
||||
})
|
||||
}
|
||||
|
||||
return &protoapi.FileList{Files: files}, nil
|
||||
}
|
||||
|
||||
func (FileTransferServer) DownloadFile(r *protoapi.DownloadRequest, stream protoapi.FileTransferService_DownloadFileServer) error {
|
||||
storeMu.RLock()
|
||||
f, exists := fileStore[r.GetFileName()]
|
||||
storeMu.RUnlock()
|
||||
|
||||
if !exists {
|
||||
return status.Errorf(codes.NotFound, "file %s not found", r.GetFileName())
|
||||
}
|
||||
|
||||
chunkSize := 1024
|
||||
totalBytes := len(f.Content)
|
||||
|
||||
for i := 0; i < totalBytes; i += chunkSize {
|
||||
end := i + chunkSize
|
||||
if end > totalBytes {
|
||||
end = totalBytes
|
||||
}
|
||||
|
||||
err := stream.Send(&protoapi.FileChunk{
|
||||
FileName: f.FileName,
|
||||
Content: f.Content[i:end],
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// 3. Alert Server Implementation
|
||||
type AlertServer struct {
|
||||
protoapi.UnimplementedAlertServiceServer
|
||||
}
|
||||
|
||||
func (AlertServer) SubscribeAlerts(r *protoapi.AlertSubscription, stream protoapi.AlertService_SubscribeAlertsServer) error {
|
||||
clientId := r.GetClientId()
|
||||
ch := make(chan *protoapi.AlertMessage, 10)
|
||||
sub := &AlertSubscriber{
|
||||
ClientId: clientId,
|
||||
Channel: ch,
|
||||
}
|
||||
|
||||
subMu.Lock()
|
||||
subscribers[clientId] = sub
|
||||
subMu.Unlock()
|
||||
|
||||
fmt.Printf("[Modular Alert] Client %s subscribed to alerts on topic '%s'\n", clientId, r.GetTopic())
|
||||
|
||||
for {
|
||||
select {
|
||||
case alert := <-ch:
|
||||
err := stream.Send(alert)
|
||||
if err != nil {
|
||||
subMu.Lock()
|
||||
delete(subscribers, clientId)
|
||||
subMu.Unlock()
|
||||
fmt.Printf("[Modular Alert] Client %s alert subscription disconnected: %v\n", clientId, err)
|
||||
return err
|
||||
}
|
||||
case <-stream.Context().Done():
|
||||
subMu.Lock()
|
||||
delete(subscribers, clientId)
|
||||
subMu.Unlock()
|
||||
fmt.Printf("[Modular Alert] Client %s unsubscribed (context done)\n", clientId)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func ServerRun(addr string) {
|
||||
server := grpc.NewServer()
|
||||
|
||||
// Register multiple services on the same gRPC server
|
||||
protoapi.RegisterCoreServiceServer(server, CoreServer{})
|
||||
protoapi.RegisterFileTransferServiceServer(server, FileTransferServer{})
|
||||
protoapi.RegisterAlertServiceServer(server, AlertServer{})
|
||||
|
||||
reflection.Register(server)
|
||||
|
||||
listen, err := net.Listen("tcp", addr)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Printf("[Modular Server] Listening on %s...\n", addr)
|
||||
server.Serve(listen)
|
||||
}
|
||||
Reference in New Issue
Block a user