이전에 프로토콜 버퍼 개념과 간단히 데이터 구조를 만드는 실습을 진행했다.
이번에는 프로토콜 버퍼를 사용해 Rest API 를 구현해보려한다.
Java + Spring Boot + Maven 실습
프로젝트 셋팅은 그전 블로깅 내용과 유사하다. 자세한 설명은 거기서 참고..
서버간의 Restful HTTP 통신이기때문에 똑같은 프로젝트를 하나 더 만들어줬다.
protoc라는 이름의 프로젝트는 서버포트 8091을 가지는 데이터를 요청하는 곳이고
protoc2라는 이름의 프로젝트는 서버포트 8080을 가지는 데이터를 가지고 보내는 곳이다.
Pom.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
|
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.google.protobuf</groupId>
<artifactId>protobuf-java</artifactId>
<version>3.21.9</version>
</dependency>
</dependencies>
<build>
<extensions>
<extension>
<groupId>kr.motd.maven</groupId>
<artifactId>os-maven-plugin</artifactId>
<version>1.6.1</version>
</extension>
</extensions>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.xolstice.maven.plugins</groupId>
<artifactId>protobuf-maven-plugin</artifactId>
<version>0.6.1</version>
<extensions>true</extensions>
<executions>
<execution>
<goals>
<goal>compile</goal>
<goal>test-compile</goal>
</goals>
</execution>
</executions>
<configuration>
<protocArtifact>com.google.protobuf:protoc:3.21.9:exe:${os.detected.classifier}</protocArtifact>
<pluginId>grpc-java</pluginId>
<pluginArtifact>io.grpc:protoc-gen-grpc-java:1.25.0:exe:${os.detected.classifier}</pluginArtifact>
<clearOutputDirectory>false</clearOutputDirectory>
<protoSourceRoot>${project.basedir}/src/main/resources/proto</protoSourceRoot>
<outputDirectory>${project.basedir}/src/main/java/</outputDirectory>
</configuration>
</plugin>
</plugins>
</build>
|
cs |
Person.proto
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
syntax = "proto3";
package com.test.protoc.domain;
option java_outer_classname = "PersonInfo";
message Person {
string name = 1;
int32 id = 2;
string email = 3;
}
message AddPerson {
repeated Person people = 1;
}
|
cs |
package명만 잘 맞춰주자..
그리고 서버 포트를 각각 설정해줬다.
application.properties
- protoc
1
|
server.port=8091
|
cs |
- protoc2
1
|
server.port=8080
|
cs |
이제 각각 프로젝트를 나눠서 작성해보겠다.
데이터를 가지고 전송해주는 서버쪽 (protoc2)
RestTestController
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
import com.test.protoc2.domain.PersonInfo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
@Controller
public class RestTestController {
@Autowired
RestTestService service;
@GetMapping("/rest/test")
public ResponseEntity<Object> rest() {
PersonInfo.AddPerson people = service.dummyData();
return new ResponseEntity<>(people, HttpStatus.OK);
}
}
|
cs |
일단 컨트롤러에서 요청을 받을 메소드를 생성해준다.
이 메소드는 배열 형태의 Person을 가지는 AddPerson이라는 데이터 구조를 리턴해준다.
RestTestService
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
import com.test.protoc2.domain.PersonInfo;
import org.springframework.stereotype.Service;
@Service
public class RestTestService {
public PersonInfo.AddPerson dummyData() {
PersonInfo.Person person1 = PersonInfo.Person.newBuilder().setId(1).setName("홍길동").setEmail("hong11@test.com").build();
PersonInfo.Person person2 = PersonInfo.Person.newBuilder().setId(2).setName("고길동").setEmail("go22@test.com").build();
PersonInfo.Person person3 = PersonInfo.Person.newBuilder().setId(3).setName("오길동").setEmail("oh33@test.com").build();
PersonInfo.AddPerson people = PersonInfo.AddPerson.newBuilder().addPeople(person1).addPeople(person2).addPeople(person3).build();
return people;
}
}
|
cs |
서비스단에서 더미데이터를 만들어줬다.
데이터를 요청하는 서비스쪽 (protoc)
여기에서는 따로 controller와 service를 만들어주지 않고 main 메소드에서 직접 호출해줬다.
ProtocApplication
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
|
import com.test.protoc.domain.PersonInfo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.util.UriComponentsBuilder;
@SpringBootApplication
public class ProtocApplication {
public static void main(String[] args) {
SpringApplication.run(ProtocApplication.class, args);
RestTemplate restTemplate = new RestTemplate();
String uri = UriComponentsBuilder.fromHttpUrl("http://localhost:8080/rest/test").toUriString();
PersonInfo.AddPerson people = restTemplate.getForEntity(uri, PersonInfo.AddPerson.class).getBody();
for (PersonInfo.Person p : people.getPeopleList()) {
System.out.println(p.getId());
System.out.println(p.getName());
System.out.println(p.getEmail());
System.out.println(p.getAllFields());
}
}
}
|
cs |
이대로 각각 실행시켜주면~
잘될줄 알았는데...! 삽질 시작...
Exception in thread "main" org.springframework.web.client.UnknownContentTypeException: Could not extract response: no suitable HttpMessageConverter found for response type [class com.test.protoc.domain.PersonInfo$AddPerson] and content type [application/x-protobuf;charset=UTF-8]
적절한 MessageConverter가 없다고 에러가 난다.
baeldung 튜토리얼에서 답을 찾았다.
- protoc
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
|
import com.test.protoc.domain.PersonInfo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.http.HttpHeaders;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.protobuf.ProtobufHttpMessageConverter;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.util.UriComponentsBuilder;
import java.util.ArrayList;
import java.util.List;
@SpringBootApplication
public class ProtocApplication {
public static void main(String[] args) {
SpringApplication.run(ProtocApplication.class, args);
ProtobufHttpMessageConverter protobufHttpMessageConverter = new ProtobufHttpMessageConverter();
List<HttpMessageConverter<?>> converters = new ArrayList<HttpMessageConverter<?>>();
converters.add(protobufHttpMessageConverter);
RestTemplate restTemplate = new RestTemplate(converters);
HttpHeaders headers = new HttpHeaders();
String uri = UriComponentsBuilder.fromHttpUrl("http://localhost:8080/rest/test").toUriString();
PersonInfo.AddPerson people = restTemplate.getForEntity(uri, PersonInfo.AddPerson.class).getBody();
for (PersonInfo.Person p : people.getPeopleList()) {
System.out.println(p.getId());
System.out.println(p.getName());
System.out.println(p.getEmail());
System.out.println(p.getAllFields());
}
}
}
|
cs |
요청하는 쪽에 RestTemplate에 받아오는 데이터를 converte 해줄 ProtobufHttpMessageConverter을 추가해준다.
- protoc2
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.http.converter.protobuf.ProtobufHttpMessageConverter;
@SpringBootApplication
public class Protoc2Application {
@Bean
ProtobufHttpMessageConverter protobufHttpMessageConverter() {
return new ProtobufHttpMessageConverter();
}
public static void main(String[] args) {
SpringApplication.run(Protoc2Application.class, args);
}
}
|
cs |
데이터를 주는 쪽에서도 ProtobufHttpMessageConverter가 필요하다. 빈으로 주입해준다.
안해주면 만든 데이터가 담기지 않아 no body 에러가 난다.
Exception in thread "main" org.springframework.web.client.HttpClientErrorException$NotAcceptable: 406 : [no body]
이렇게 실행해주면 둘 사이에 요청-응답을 지나 가져온 데이터를 잘 찍히는 지 볼 수 있다.
이렇게 프로토콜 버퍼를 사용해 통신을 하면 다양한 언어에서 호환 가능하고 바이너리 전송을 통해 속도 개선의 이점을 챙길 수 있을 것이다.
코드
https://github.com/recordbuffer/TIL/tree/main/ProtocolBuffer
참고
Spring REST API with Protocol Buffers | Baeldung
'기타 학습' 카테고리의 다른 글
디버깅 (개념 / 과정 / Intellij 디버깅 방법 / Eclipse 디버깅 방법) (0) | 2023.04.19 |
---|---|
[Google Trends] 구글의 키워드 검색 서비스 (소개 / 기능 / API / Pytrends) (0) | 2023.04.07 |
[AWS] Access Key노출 (AWSCompromisedKeyQuarantineV2 해결) (1) | 2022.11.30 |
[Protocol Buffer] 프로토콜 버퍼 (개념 / 입문 / Spring Boot Maven 실습) (0) | 2022.11.28 |
[Flutter] Flutter widget (위젯 개념/ State) (0) | 2022.09.10 |
댓글