docs: restore missing UploadFile server/client handler documentation in GRPC.md

This commit is contained in:
2026-07-17 19:53:05 +09:00
parent d83fe114d0
commit 9de3f5144d
+76 -4
View File
@@ -344,7 +344,47 @@ var (
storeMu sync.RWMutex storeMu sync.RWMutex
) )
// 2. 파일 리스트 조회 Unary RPC // 2. 파일 업로드 Client Streaming RPC
func (IoTServer) UploadFile(stream protoapi.IoTService_UploadFileServer) error {
var totalBytes int64
var fileName string
var buffer []byte
for {
chunk, err := stream.Recv()
if err == io.EOF {
fmt.Printf("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("File upload error:", err)
return err
}
if fileName == "" {
fileName = chunk.GetFileName()
}
buffer = append(buffer, chunk.GetContent()...)
totalBytes += int64(len(chunk.GetContent()))
}
}
// 3. 파일 리스트 조회 Unary RPC
func (IoTServer) ListFiles(ctx context.Context, r *protoapi.EmptyRequest) (*protoapi.FileList, error) { func (IoTServer) ListFiles(ctx context.Context, r *protoapi.EmptyRequest) (*protoapi.FileList, error) {
storeMu.RLock() storeMu.RLock()
defer storeMu.RUnlock() defer storeMu.RUnlock()
@@ -360,7 +400,7 @@ func (IoTServer) ListFiles(ctx context.Context, r *protoapi.EmptyRequest) (*prot
return &protoapi.FileList{Files: files}, nil return &protoapi.FileList{Files: files}, nil
} }
// 3. 파일 다운로드 Server Streaming RPC // 4. 파일 다운로드 Server Streaming RPC
func (IoTServer) DownloadFile(r *protoapi.DownloadRequest, stream protoapi.IoTService_DownloadFileServer) error { func (IoTServer) DownloadFile(r *protoapi.DownloadRequest, stream protoapi.IoTService_DownloadFileServer) error {
storeMu.RLock() storeMu.RLock()
f, exists := fileStore[r.GetFileName()] f, exists := fileStore[r.GetFileName()]
@@ -390,9 +430,11 @@ func (IoTServer) DownloadFile(r *protoapi.DownloadRequest, stream protoapi.IoTSe
} }
``` ```
* **쉬운 설명**: * **쉬운 설명**:
* **파일 업로드**: 클라이언트가 쪼개서 던지는 파일 조각 상자(`stream.Recv()`)들을 루프를 돌며 계속 수집하여 하나의 임시 보관 버퍼(`buffer`)에 합칩니다. 마지막 조각 전송 완료(`io.EOF`) 신호가 오면, 모인 바이트들을 파일 이름과 함께 서버의 보관함(`fileStore`)에 안전하게 저장하고 영수증을 클라이언트에게 발행합니다.
* **목록 조회**: 서버의 파일 보관함(`fileStore`)을 열고 그 안에 든 모든 파일의 메타데이터(이름, 크기, 업로드 시각)를 리스트로 포장해 한번에 리턴해 줍니다. * **목록 조회**: 서버의 파일 보관함(`fileStore`)을 열고 그 안에 든 모든 파일의 메타데이터(이름, 크기, 업로드 시각)를 리스트로 포장해 한번에 리턴해 줍니다.
* **파일 다운로드**: 보관함에서 요청받은 파일을 찾은 뒤, 파일 내용 전체를 1KB 크기의 패킷 조각들로 잘라 통로를 타고 차례대로 연속 전송(`stream.Send()`)해 줍니다. * **파일 다운로드**: 보관함에서 요청받은 파일을 찾은 뒤, 파일 내용 전체를 1KB 크기의 패킷 조각들로 잘라 통로를 타고 차례대로 연속 전송(`stream.Send()`)해 줍니다.
* **상세 설명**: * **상세 설명**:
* **파일 업로드**: 수신 파이프라인 스트림의 `Recv()`를 호출하여 개별 `FileChunk` 객체들을 수령합니다. 수신 스트림이 종료(`io.EOF`)되면 버퍼링된 바이트 데이터와 타임스탬프를 묶어 스레드 동기화 락(`storeMu.Lock()`)을 획득하고 인메모리 맵에 적재한 뒤, `SendAndClose`로 마감 처리합니다.
* **목록 조회**: 동시 접근 보호(Race condition 방지)를 위해 읽기 전용 락(`RLock`)을 획득한 후 인메모리 맵을 순회하며 메타데이터 구조체 목록을 집계해 반환합니다. * **목록 조회**: 동시 접근 보호(Race condition 방지)를 위해 읽기 전용 락(`RLock`)을 획득한 후 인메모리 맵을 순회하며 메타데이터 구조체 목록을 집계해 반환합니다.
* **파일 다운로드**: 대상 파일 쿼리 실패 시 gRPC 표준 에러(`codes.NotFound`)를 반환합니다. 검증 통과 시 루프 내에서 가상 윈도우 슬라이싱을 집행해 청크 구조체를 구성하고, `stream.Send()`로 직렬화 패킷을 클라이언트 버퍼 큐에 기입합니다. * **파일 다운로드**: 대상 파일 쿼리 실패 시 gRPC 표준 에러(`codes.NotFound`)를 반환합니다. 검증 통과 시 루프 내에서 가상 윈도우 슬라이싱을 집행해 청크 구조체를 구성하고, `stream.Send()`로 직렬화 패킷을 클라이언트 버퍼 큐에 기입합니다.
@@ -400,12 +442,40 @@ func (IoTServer) DownloadFile(r *protoapi.DownloadRequest, stream protoapi.IoTSe
클라이언트는 업로드에 성공한 뒤, 서버에 파일 목록 조회를 요구하고, 다운로드 스트림을 개설해 조각 데이터를 재조립하여 무결성을 검사합니다. 클라이언트는 업로드에 성공한 뒤, 서버에 파일 목록 조회를 요구하고, 다운로드 스트림을 개설해 조각 데이터를 재조립하여 무결성을 검사합니다.
```go ```go
// 1. 파일 목록 조회 호출 // 1. 파일 업로드 송신 (클라이언트 스트리밍)
func AskUploadFile(ctx context.Context, m protoapi.IoTServiceClient, fileName string, dummyData []byte) (*protoapi.UploadStatus, error) {
stream, err := m.UploadFile(ctx)
if err != nil {
return nil, err
}
chunkSize := 1024 // 1KB 단위 분할 송신
totalBytes := len(dummyData)
for i := 0; i < totalBytes; i += chunkSize {
end := i + chunkSize
if end > totalBytes {
end = totalBytes
}
err := stream.Send(&protoapi.FileChunk{
FileName: fileName,
Content: dummyData[i:end],
})
if err != nil {
return nil, err
}
}
return stream.CloseAndRecv()
}
// 2. 파일 목록 조회 호출
func AskListFiles(ctx context.Context, m protoapi.IoTServiceClient) (*protoapi.FileList, error) { func AskListFiles(ctx context.Context, m protoapi.IoTServiceClient) (*protoapi.FileList, error) {
return m.ListFiles(ctx, &protoapi.EmptyRequest{}) return m.ListFiles(ctx, &protoapi.EmptyRequest{})
} }
// 2. 파일 다운로드 수신 및 조립 // 3. 파일 다운로드 수신 및 조립
func AskDownloadFile(ctx context.Context, m protoapi.IoTServiceClient, fileName string) ([]byte, error) { func AskDownloadFile(ctx context.Context, m protoapi.IoTServiceClient, fileName string) ([]byte, error) {
stream, err := m.DownloadFile(ctx, &protoapi.DownloadRequest{FileName: fileName}) stream, err := m.DownloadFile(ctx, &protoapi.DownloadRequest{FileName: fileName})
if err != nil { if err != nil {
@@ -427,9 +497,11 @@ func AskDownloadFile(ctx context.Context, m protoapi.IoTServiceClient, fileName
} }
``` ```
* **쉬운 설명**: * **쉬운 설명**:
* **파일 업로드**: 보낼 파일을 1KB 조각 크기로 나누어 준비한 뒤, gRPC 전용 파이프 스트림 통로에 차례대로 흘려보냅니다(`stream.Send()`). 모든 조각을 던진 후 채널을 끊고 영수증(`UploadStatus`)을 받습니다.
* **목록 조회**: 서버에게 "보관 중인 파일 이름 목록을 달라"고 요구하여 화면에 출력합니다. * **목록 조회**: 서버에게 "보관 중인 파일 이름 목록을 달라"고 요구하여 화면에 출력합니다.
* **파일 다운로드**: 다운로드 통로를 열어 서버가 던져주는 조각들을 계속 수령(`stream.Recv()`)하여 버퍼에 차곡차곡 합칩니다. 서버가 보내기를 끝마치면(`io.EOF`) 조립을 중단하고 최종 완성된 온전한 바이트 파일을 최종 사용처에 반환합니다. * **파일 다운로드**: 다운로드 통로를 열어 서버가 던져주는 조각들을 계속 수령(`stream.Recv()`)하여 버퍼에 차곡차곡 합칩니다. 서버가 보내기를 끝마치면(`io.EOF`) 조립을 중단하고 최종 완성된 온전한 바이트 파일을 최종 사용처에 반환합니다.
* **상세 설명**: * **상세 설명**:
* **파일 업로드**: `UploadFile` 채널을 기동하여 클라이언트 사이드 스트림 핸들을 획득합니다. 슬라이스 윈도우 방식으로 데이터를 순차 분할 송출하고, `CloseAndRecv` 메서드를 최종 호출해 스트림 종결 프레임을 송신한 뒤 서버의 단발성 최종 회신 상태를 획득합니다.
* **목록 조회**: 빈 메시지(`EmptyRequest`)를 동봉해 Unary RPC 채널을 트리거하고 메타데이터 배열 결과를 동기 획득합니다. * **목록 조회**: 빈 메시지(`EmptyRequest`)를 동봉해 Unary RPC 채널을 트리거하고 메타데이터 배열 결과를 동기 획득합니다.
* **파일 다운로드**: 서버 스트리밍 엔드포인트 기동 후, `stream.Recv()` 블로킹 수신 루프에 진입합니다. 채널 해제 지점(`io.EOF`)에 도달할 때까지 메모리 버퍼 슬라이스에 청크 바이트 배열을 병합 누적하여 재조립(Reassembly)을 마친 후 반환합니다. * **파일 다운로드**: 서버 스트리밍 엔드포인트 기동 후, `stream.Recv()` 블로킹 수신 루프에 진입합니다. 채널 해제 지점(`io.EOF`)에 도달할 때까지 메모리 버퍼 슬라이스에 청크 바이트 배열을 병합 누적하여 재조립(Reassembly)을 마친 후 반환합니다.