MSA구조에서 쿠키를 다루다가 CORS (Cross-Origin Resource Sharing) 문제를 피하기 위해서

https로 변경이 필요해서 시도해 보게 되었다.

서버사이드는 여기 참조

 

먼저 인증서를 발급한다.


개발용 인증서를 생성하기 위해 openssl을 사용할 수 있습니다. 터미널에서 아래 명령을 실행

openssl req -x509 -newkey rsa:4096 -keyout key.pem -out cert.pem -days 365

암호를 입력하라는 창이뜬다.

이 명령은 1년 동안 유효한 cert.pem과 key.pem 파일을 생성

 

 

 

기존에 npm start로 시작했다면, 아래처럼 start대신 server.js로 변경

 

다음처럼 server.js를 작성하고, project root에 둔다.

console.log('sevity Server is starting...');

const { createServer } = require('https');
const { parse } = require('url');
const next = require('next');
const fs = require('fs');

const dev = process.env.NODE_ENV !== 'production';
const app = next({ dev });
const handle = app.getRequestHandler();

app.prepare().then(() => {
    const options = {
        key: fs.readFileSync('./key.pem'),
        cert: fs.readFileSync('./cert.pem'),
        passphrase: 'abcd123$'
    };

    createServer(options, (req, res) => {
        const parsedUrl = parse(req.url, true);
        handle(req, res, parsedUrl);
    }).listen(9992, (err) => {
        if (err) throw err;
        console.log('> Ready on https://localhost:9992');
    });
});

 

npm start로 시작해보면 http대신 https로 되는걸 확인가능.

반응형

'Programming > node.js' 카테고리의 다른 글

node.js/vscode 환경에서 디버깅 환경 구축하기  (0) 2023.08.06

MSA구조에서 Spring Service간 통신에 REST를 써도되지만 gRPC를 쓸 수도 있다.

이경우 설정방법에 대해서 경험한 바를 여기 적는다.

 

pom.xml 에 의존성 추가

서버/클라이언트 공통

아래 내용을 서버/클라이언트 pom.xml에 공히 추가하면 되고,

src/main/kotlin/이냐 src/main/java/냐 이부분만 서로 다르게 수정해주면 된다.

protobuf-javacom.google.protobuf:protoc:3.12.4 이부분등 버전을 맞춰주지 않으면 빌드과정에서 오류가 나는 경우가 있었으니 주의.

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <dependencies>

        <!-- gRPC -->
        <dependency>
            <groupId>com.google.protobuf</groupId>
            <artifactId>protobuf-java</artifactId>
            <version>3.12.4</version>  <!--이 버전을 아래쪽 ptoroc버전과 맞춰야 함-->
        </dependency>
        <dependency>
            <groupId>io.grpc</groupId>
            <artifactId>grpc-netty-shaded</artifactId>
            <version>1.41.0</version>
        </dependency>
        <dependency>
            <groupId>io.grpc</groupId>
            <artifactId>grpc-protobuf</artifactId>
            <version>1.41.0</version>
        </dependency>
        <dependency>
            <groupId>io.grpc</groupId>
            <artifactId>grpc-stub</artifactId>
            <version>1.41.0</version>
        </dependency>
    </dependencies>

    <build>
        <extensions>
            <extension>
                <!--Maven 빌드 과정 중에 운영 체제(OS)에 관한 정보를 제공하고 설정하는 데 도움을 줍니다. -->
                <groupId>kr.motd.maven</groupId>
                <artifactId>os-maven-plugin</artifactId>
                <version>1.7.0</version>
            </extension>
        </extensions>
        <plugins>
            <plugin>
                <groupId>org.xolstice.maven.plugins</groupId>
                <artifactId>protobuf-maven-plugin</artifactId>
                <version>0.6.1</version>
                <configuration>
                    <protocArtifact>com.google.protobuf:protoc:3.12.4:exe:${os.detected.classifier}</protocArtifact>
                    <pluginId>grpc-java</pluginId>
                    <pluginArtifact>io.grpc:protoc-gen-grpc-java:1.51.0:exe:${os.detected.classifier}</pluginArtifact>
                    <outputBaseDirectory>src/main/kotlin/</outputBaseDirectory>
                    <outputDirectory>src/main/kotlin/</outputDirectory>
                    <clearOutputDirectory>false</clearOutputDirectory>
                </configuration>
                <executions>
                    <execution>
                        <goals>
                            <goal>compile</goal>
                            <goal>compile-custom</goal>
                        </goals>
                        <configuration>
                        </configuration>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>

</project>

 

클라이언트 전용

<dependency>
    <groupId>net.devh</groupId>
    <artifactId>grpc-client-spring-boot-autoconfigure</artifactId>
    <version>2.15.0.RELEASE</version>
</dependency>

위의 내용은 필수는 아니나 코드가 좀 더 깔끔해지게 도와준다.

 

protobuf 파일추가

아래와 같이 .proto 파일을 제작해서 src/main/proto 폴더안에 둔다(다른곳에 두어도 되며, 프로젝트간 공용위치에 두어도 된다)

//session_service.proto
// Version: 1.0.0

syntax = "proto3";

package com.sevity.authservice.grpc;

service SesseionService {
    rpc GetUserId (SessionRequest) returns (UserResponse) {};
}

message SessionRequest {
    string sessionId = 1;
}

message UserResponse {
    int32 userId = 1;
}

중요한건 아래 트러블슈팅에서도 나오지만 package명을 서버/클라이언트가 다르게 하면 못찾는다는 오류가 떴다.

 

빌드

mvn clean install 등을 수행하면 .proto 파일을 컴파일해서 다음과 같은 2개의 java파일을 자동으로 생성해준다.

위치또한 pom.xml파일에 지정된 대로 생성된다.

파일2개중 하나만 생성된적도 있었는데 여기보고 해결했던것 같다.

protoc를 터미널에서 직접 사용해서 .proto파일을 컴파일하는 것도 가능하긴하지만, mvn에 통합해서 운용하는게 훨씬 편하고, protoc 사용과정에서 직접빌드해야하는등 우여곡절도 발생했다.

서버코드 제작

아래처럼 생성된 java파일들 (grpc패키지)을 import해주고,

gRPC서버측 구현을 해준다(SessionService의 getUserId 함수)

package com.sevity.authservice.service;

import com.sevity.authservice.grpc.SesseionServiceGrpc;
import com.sevity.authservice.grpc.SessionService.SessionRequest;
import com.sevity.authservice.grpc.SessionService.UserResponse;

import io.grpc.StatusRuntimeException;
import io.grpc.stub.StreamObserver;
import io.grpc.Status;


@Service
public class SessionServiceImpl extends SesseionServiceGrpc.SesseionServiceImplBase {
    @Override
    public void getUserId(SessionRequest request, StreamObserver<UserResponse> responseObserver) {
        String sessionId = request.getSessionId();

        UserResponse response = UserResponse.newBuilder().setUserId(sessionId).build();
        responseObserver.onNext(response);
        responseObserver.onCompleted();
    }
}

 

application.properties에서 포트설정해주고(이때 src/main/resources뿐 아니라 src/test/resources에 있는 파일도 해줘야함에 주의)

# in application.properties
grpc.server.port = 50051

 

다음처럼 gRPC서버 띄욱 listen작업도 해줘야 했다.

package com.sevity.authservice.config;

import ...
@Configuration
public class GrpcServerConfig {
    private static final Logger logger = LoggerFactory.getLogger(GrpcServerConfig.class);

    @Autowired
    private SessionServiceImpl sessionService;
    
    private Server server;
    
    @Value("${grpc.server.port}")
    private int port;

    @PostConstruct
    public void startServer() throws IOException {
        server = ServerBuilder
            .forPort(port)
            .addService(sessionService)  // Your gRPC service implementation
            .build();

        server.start();
        logger.info("sevity gRPC server started on port {}", port);
        logger.info("sevity gRPC service name: {}", sessionService.getClass().getSimpleName());        
    }

    @PreDestroy
    public void stopServer() {
        if (server != null) {
            server.shutdown();
        }
        logger.info("sevity gRPC server stopped");
    }
}

 

클라이언트코드 제작

application.properties에 아래줄 추가

# GRPC
grpc.client.authService.address=static://sevity.com:50051
grpc.client.authService.negotiationType=PLAINTEXT

 

빌드

maven,kotlin환경이었는데, 빌드과정은 서버측과 큰차이가 없다.(같은 방법으로 빌드하면 된다)

(생성되는 파일도 여전히 .java이며 kotlin과 통합에 문제가 없었다)

 

호출하는 코드는 아래와 같다(코틀린임에 주의)

/submit 매핑과, cookie, session관련 처리때문에 복잡하나 그 부분은 제외하고 gRPC stub에 대한 내용만 눈여겨보자.

(서버측에 비해서 필요할때 문맥중간에서 요청하게된다)

@RestController
class SubmissionController {

    @GrpcClient("authService")
    private lateinit var sessionServiceStub: SesseionServiceGrpc.SesseionServiceBlockingStub

    @PostMapping("/submit")
    fun submitCode(request: HttpServletRequest): ResponseEntity<String> {
        val cookies = request.cookies
        val sessionId = cookies?.find { it.name == "SESSION" }?.value
            ?: return ResponseEntity("Session ID not found", HttpStatus.UNAUTHORIZED)

        val request2 = SessionRequest.newBuilder().setSessionId(sessionId).build()
        try {
            val response = sessionServiceStub.getUserId(request2)
            println("Received user ID: ${response.userId}")
            return ResponseEntity("Code submitted", HttpStatus.OK)
        } catch (e: InvocationTargetException) {
            e.targetException.printStackTrace()
        }

        // 나머지 로직
        return ResponseEntity("Code submitted", HttpStatus.OK)
    }
}

 

 

트러블슈팅

io.grpc.StatusRuntimeException: UNIMPLEMENTED: Method not found

.proto 파일은 파일내 package경로까지 완전히 동일한 파일을 사용하지 않으면 못찾는다고 에러가 났다.

이거때문에 한참헤멤 ㅠ 여기참조. 유일한 솔루션인지는 잘 모르겠으나,

서버기준으로 package명까지 동일하게 맞춰주니 해결됨.

 

 

반응형

개요

gRPC는 Google이 개발한 고성능, 오픈 소스 및 범용의 원격 프로시저 호출(RPC) 프레임워크입니다. 

효율적인 프로토콜로, 서버 간 통신에 아주 적합하며, Protocol Buffers를 사용하여 타입을 정의하고, 강력한 타입 검사와 높은 성능을 제공합니다.
이는 서로 다른 시스템 간에 통신을 가능하게 하며, 다양한 환경과 언어에서 작동합니다. 

 

gRPC는 2015년 3월에 Google이 Stubby의 다음 버전을 개발하고 오픈 소스로 만들기로 결정했을 때 처음 생성되었습니다. gRPC의 최초 릴리스는 2016년 8월에 이루어졌습니다​. 현재 gRPC의 최신 버전은 1.59.1(2023년 10월 6일기준)

 

장점:

  • 기존의 REST등 텍스트 기반 프로토콜보다 더 효율적인 바이너리 프로토콜을 제공하여, 데이터 전송의 오버헤드를 줄이고 성능을 향상시킵니다.   
  • 컨트랙트 첫 접근 방식: 서비스의 인터페이스와 메시지를 먼저 정의하고, 이를 기반으로 코드를 생성합니다.
    (Protocol Buffers를 사용하여 데이터를 직렬화하고 역직렬화하여, 높은 성능을 제공합니다.)
  • 스트리밍 및 빠른 통신: 양방향 스트리밍과 빠른 통신을 지원하여, 실시간 애플리케이션에 이상적입니다.
  • 비동기 콜도 지원하는 것 같다.

단점:

  • 복잡성: gRPC는 설정과 디버깅이 복잡할 수 있으며, 새로운 사용자에게 진입 장벽을 제공할 수 있습니다.
  • 텍스트 기반 포맷의 부족: gRPC는 바이너리 프로토콜을 사용하므로, 텍스트 기반 프로토콜보다 디버깅이 어려울 수 있습니다.
  • 브라우저 지원: gRPC-Web을 통해 브라우저에서 gRPC를 사용할 수 있지만, 네이티브 gRPC 클라이언트보다 기능이 제한적일 수 있습니다.

 

vs REST

REST는 HTTP/1.1을 기반으로 하며, 텍스트 기반의 JSON 또는 XML을 사용하여 데이터를 전송합니다. 이에 비해 gRPC는 HTTP/2를 기반으로 하며, 바이너리 기반의 Protocol Buffers를 사용합니다.
gRPC는 REST보다 더 높은 성능과 더 낮은 데이터 오버헤드를 제공하지만, REST는 더 단순하고 더 넓게 지원됩니다.

 

 

spring과 연동하기

여기 참조

 

TMI

굳이 protobuf를 별도 포맷으로 했는데(yml, json, xml등을 사용하지 않고), verbose하지 않고 간결한 것이 장점인 것 같다. (다른 이유는 굳이 없는듯)

gRPC는 일반적으로 REST와 별개 포트로 구성(graphQL은 REST와 같은 포트로 보통하는듯)

반응형

'System Architect' 카테고리의 다른 글

Application  (0) 2023.10.28
graphQL  (0) 2023.10.12
시스템설계 Q&A 2  (0) 2023.09.20
데이터 분석 관련 정리  (0) 2023.08.19
시스템설계 Q&A  (0) 2023.08.08

+ Recent posts