---
title: Multiplay 서버에서 HTTP 요청하기
slug: world-sdk-guide-kr/multiplay-http
docTags: 
createdAt: 2024-09-02T08:34:13.135Z
---

ZEPETO 월드 멀티플레이 서버에서 ZEPETO.Multiplay.HttpService 모듈을 사용하여 HTTP 요청을 할 수 있습니다. 비즈니스 로직 작업, 데이터 저장, 통계 분석, 오류 추적 등을 위해 외부 웹 서비스를 통합하세요.



:::hint{type="danger"}
- HTTPS 프로토콜만 사용되도록 하세요. HTTP는 개발 환경에서만 지원됩니다.
- 요청은 포트 80과 443에서만 허용됩니다.
- 요청 및 응답 본문의 최대 크기는 16KB로 제한됩니다.
- 과도한 요청이 발생할 경우 월드 서비스에 대한 잠재적 제한을 피하기 위해 분당 요청 수를 500 이하로 유지하세요.
- 외부 웹 서비스가 5초 이내에 응답하지 않으면 요청이 실패합니다.
- 응답 헤더의 content-type이 `HttpContentType` 열거형에 정의된 값과 일치하는지 확인하세요. 그렇지 않으면 요청이 실패합니다.
- 다양한 이유로 웹 요청이 실패할 가능성이 있으므로 방어적으로 코딩하는 것이 좋습니다.
:::



## ZEPETO.Multiplay.HttpService

:::hint{type="info"}
**📘&#x20;**&#xB2E4;음 가이드를 참조하세요. \[[ZEPETO.Multiplay.HttpService API](https://developer.zepeto.me/docs/multiplay-server/interfaces/ZEPETO_Multiplay_HttpService.HttpService)]
:::



### 메서드

| **방법**                                                                                                                  | **설명**                                                                                                                                                                                                                                                                                                                                                                                             |
| ----------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| HttpService.getAsync(url: string, headers?: HttpHeader): Promise                                                        | 비동기적으로 HTTP GET 요청을 수행합니다.<br />.**\[매개변수]**<br />- url : 요청을 보낼 웹 주소입니다.<br />- headers : HTTP 요청 헤더입니다. (선택 사항)<br /><br />**\[반환 값]**<br />- Promise\<HttpResponse> : 응답에 대한 정보를 포함하는 HttpResponse 객체를 Promise로 반환합니다.                                                                                                                                                                          |
| HttpService.postAsync(url: string, body: HttpBodyType, headers?: HttpHeader): Promise                                   | 비동기적으로 HTTP POST 요청을 수행합니다.<br />.**\[매개변수]**<br />- url : 요청을 보낼 웹 주소입니다.<br />- body : 요청 본문 내용입니다.<br />- headers : HTTP 요청 헤더입니다. (선택 사항)<br /><br />**\[반환 값]**<br />- Promise\<HttpResponse> : 응답에 대한 정보를 포함하는 HttpResponse 객체를 Promise로 반환합니다.                                                                                                                                              |
| HttpService.postAsync(url: string, body: HttpBodyType, httpContentType: HttpContentType, headers?: HttpHeader): Promise | 비동기적으로 HTTP POST 요청을 수행합니다.<br />.**\[매개변수]**<br />- url : 요청을 보낼 웹 주소입니다.<br />- body : 요청 본문 내용입니다.<br />- httpContentType : 요청 Content-Type 헤더를 지정합니다.<br />- headers : HTTP 요청 헤더입니다. (선택 사항)<br /><br />**\[반환 값]**<br />- Promise\<HttpResponse> : 응답에 대한 정보를 포함하는 HttpResponse 객체를 Promise로 반환합니다.<br /><br />이 서명을 사용할 때, headers에 'Content-Type'을 추가하면 httpContentType에 지정된 내용으로 덮어씌워집니다. |

### 기타 선언

| **선언**          | **설명**                                                                                                                                                                                                                                                             |
| --------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| HttpContentType | HTTP 헤더의 Content-Type을 지정하는 상수의 열거형입니다.<br /><br />- ApplicationJson : 'application/json'<br />- ApplicationXml : 'application/xml'<br />- ApplicationUrlEncoded : 'application/x-www-form-urlencoded'<br />- TextPlain : 'text/plain'<br />- TextXml : 'text/xml' |
| HttpBodyType    | HTTP 요청 본문 콘텐츠의 유형으로, 문자열 또는 문자열 키와 임의의 값을 가진 객체일 수 있습니다.                                                                                                                                                                                                          |
| HttpHeader      | HTTP 요청 헤더를 정의하는 유형으로, 속성 값은 객체 내에서 문자열 또는 숫자일 수 있습니다.                                                                                                                                                                                                             |
| HttpResponse    | HTTP 요청 결과 및 응답 데이터에 대한 정보를 포함하는 인터페이스입니다.<br /><br />- statusCodeHTTP : 응답의 상태 코드를 나타내는 숫자입니다. 일반적으로 200은 성공적인 요청을 나타냅니다.<br />- statusTextHTTP : 응답의 상태 메시지를 나타내는 문자열입니다. 일반적으로 "OK"는 성공적인 요청을 나타냅니다.<br />- response : HTTP 응답 본문 데이터가 포함된 문자열입니다.              |

:::hint{type="info"}
**📘 HTTP 상태**
[https://developer.mozilla.org/en-US/docs/Web/HTTP/Status](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status)
:::



## 코드 샘플

### 기본 GET 요청

간단한 GET 요청 예제를 만들어 보겠습니다. `HttpService.getAsync`. 새로운 클라이언트가 멀티플레이 룸에 연결되면, 외부 웹 서비스에 HTTP 요청을 보내고 결과를 기록합니다.

[restcountries.com](https://restcountries.com/)는 다양한 국가에 대한 정보를 제공하는 오픈 API입니다. 우리는 이 서비스를 사용하여 일본의 수도를 알아낼 것입니다.

Multiplay를 설정한 후, World.multiplay/index.ts를 열고 서버 스크립트를 다음과 같이 작성하세요.

:::hint{type="info"}
**📘&#x20;**&#xB2E4;음 가이드를 참조하세요. \[[멀티플레이](docId\:LQdLV5a9ek5HWd16tv0Y-)]
:::



```typescript
import { Sandbox, SandboxOptions, SandboxPlayer } from "ZEPETO.Multiplay";
import { HttpService } from "ZEPETO.Multiplay.HttpService";

export default class extends Sandbox {

    onCreate(options: SandboxOptions) { }
    onJoin(client: SandboxPlayer) { this.findCapitalCity() }
    onLeave(client: SandboxPlayer, consented?: boolean) { }

    findCapitalCity() {
        // Make the request to the restcountries API
        HttpService.getAsync("https://restcountries.com/v3.1/name/japan?fields=capital")
            // Handler for the HTTP Response   
            .then((httpResponse) => {
                // Parse the JSON response data from the API (which is nested in HTTP Response)
                let countries = JSON.parse(httpResponse.response);
                console.log("The capital city of Japan is:");
                console.log(`${countries[0].capital}`);
            });
    }
}
```

코드 설명:

- 클라이언트가 멀티플레이 룸에 연결되면, `onJoin` 함수가 트리거되고, `findCapitalCity`.
- 만약 `getAsync` 호출이 restcountries API에 성공하면, `httpResponse` 객체에 접근할 수 있습니다.`then` 콜백에서.
- API 응답을 JSON 형식에서 객체로 변환하기 위해 `httpResponse.response`를 파싱합니다.
- API 응답의 구조를 참조하여 원하는 속성에 접근하고 그 값을 출력합니다.

Unity Editor에서 멀티플레이 서버를 실행하고 장면을 재생하면, 콘솔에 다음과 같은 내용이 표시됩니다.

```text
일본의 수도는: 
도쿄
```

### 방어적 코딩 관행

- 한편, 서론의 주의 사항에서 언급했듯이, 웹 요청은 웹 주소나 API 응답 형식의 변경 등 다양한 이유로 실패할 수 있습니다.
- 이러한 요청을 적절히 처리하지 않으면 월드 플레이에 다양한 부정적인 영향을 미칠 수 있으므로, 특히 HTTP 요청을 처리할 때 방어적으로 코딩하는 것이 권장됩니다.
- 다음은 위의 코드에 여러 방어적 코딩 기법을 적용한 예입니다.

```typescript
   findCapitalCity() {
    // restcountries API에 요청을 보냅니다.
    HttpService.getAsync(
        "https://restcountries.com/v3.1/name/japan?fields=capital",
        { 'Accept': HttpContentType.ApplicationJson })
        // HTTP 응답 핸들러
        .then((httpResponse) => {
            // API 호출이 200(OK)을 반환했는지 확인합니다.
            if (httpResponse.statusCode != 200) {
                // 200(OK)이 아니면 진행하지 않고 사용자 정의 오류를 발생시킵니다.
                throw (`API 오류: ${httpResponse.statusCode} ${httpResponse.statusText}`);
            }
            // API로부터 JSON 응답 데이터를 반환합니다.
            return httpResponse.response;
        })
        // API로부터 JSON 응답 데이터 핸들러
        .then((response) => {
            // JSON 응답 데이터를 파싱합니다.
            let countries = JSON.parse(response);
            // API 응답 데이터가 유효한지 확인합니다.
            if (!Array.isArray(countries) || !countries.length) {
                // 배열이 아니거나 비어있으면
                // 진행하지 않고 사용자 정의 오류를 발생시킵니다.
                throw (`API 오류: 응답 데이터가 유효하지 않습니다.`);
            }
            let country = countries[0];
            // 'capital' 필드가 유효한지 확인합니다.
            if (!country.capital) {
                // 'capital' 필드가 존재하지 않거나 비어있으면
                // 진행하지 않고 사용자 정의 오류를 발생시킵니다.
                throw (`API 오류: 'capital' 필드가 유효하지 않습니다.`);
            }
            console.log("일본의 수도는:")
            console.log(`${country.capital}`);
        })
        // getAsync 호출 및 'then' 절에서 발생하는 오류 핸들러.
        .catch((reason) => {
            console.log("API 요청 오류");
            console.log(reason);
        });
}
```

여기 적용된 방어적 코딩 기술은 다음과 같습니다:

- 다음 사용: `Accept` 헤더: `Accept` 헤더는 응답 본문의 예상 Content-Type을 지정하는 데 사용됩니다. 서버에 따라 응답 Content-Type은 `Accept` 헤더에 따라 조정될 수 있습니다.
- 확인: `HttpResponse.statusCode`: `HttpResponse.statusCode` 속성은 요청의 성공 여부를 확인하는 데 사용됩니다.
- JSON 데이터 구조 검증: `JSON.parse`로 구문 분석된 객체의 데이터 구조가 예상 구조와 일치하는지 확인합니다.
- 속성 존재 확인: 사용하려는 속성이 실제로 객체에 존재하는지 확인합니다.
- Promise의 사용: `catch` 메서드: Promise의 `catch` 메서드는 API 요청 및 응답 처리 중 발생할 수 있는 오류를 처리하는 데 사용됩니다.

이 기술들은 예기치 않은 오류에 직면했을 때 코드가 신뢰성 있게 작동하도록 보호하여 강력함을 향상시킵니다.



### 클라이언트와의 통합을 위한 룸 메시지

HTTP 요청은 멀티플레이 서버에서만 가능합니다.

그러나 멀티플레이 룸 메시지를 사용하면 클라이언트에서 서버를 트리거하여 외부 웹 서비스에 HTTP 요청을 보내고 클라이언트 내에서 응답을 활용할 수 있습니다.

다음 예제는 클라이언트-서버 통합을 보여줍니다. 이 시연에서는 클라이언트의 UI에서 버튼을 누르면 버튼에 해당하는 국가의 수도가 표시됩니다.

:::hint{type="info"}
**📘&#x20;**&#xB2E4;음 가이드를 참조하십시오. \[[Multiplay Room Message](docId\:yT8T8XPljhsCnoR5Y4R7N)]
:::



![](https://api.archbee.com/api/optimize/fCt3n1oCa8rgNJ8fw9I2N-tfcubL3eArDSHrPB-D9AO-20240904-102411.gif)



**클라이언트 코드**

```typescript
import { ZepetoScriptBehaviour } from 'ZEPETO.Script'
import { Room } from 'ZEPETO.Multiplay'
import { ZepetoWorldMultiplay } from 'ZEPETO.World';
import { Button, Text } from 'UnityEngine.UI'

export default class SampleScript extends ZepetoScriptBehaviour {

    public multiplay: ZepetoWorldMultiplay;
    public room: Room;
    public countryButtons: Button[];
    public capitalText: Text;


    Start() {
        interface Response {
            capitals: string[]
        }

        for (const countryButton of this.countryButtons) {
            countryButton.onClick.AddListener(() => {
                if (this.room !== null) {
                    // Send the country name as a 'client-to-server' type message to the server
                    this.room.Send("client-to-server", countryButton.GetComponentInChildren<Text>().text);
                }
            });
        };

        this.multiplay.RoomCreated += (room: Room) => {
            this.room = room;
            // Handle messages received with a 'server-to-client' type
            this.room.AddMessageHandler("server-to-client", (message: Response) => {
                // For countries with multiple capital cities,
                // join the string elements with ', ' and display on the scene
                this.capitalText.text = message.capitals.join(", ");
            });
        };
    }
}
```

코드 설명:

- 우리는 국가 이름을 표시하는 버튼을 반복하는 리스너를 정의합니다. 클릭하면 이 리스너는 멀티플레이 서버에 방 메시지를 보냅니다.
- 이 리스너는 버튼에 표시된 국가 이름을 '클라이언트-서버' 유형 메시지로 보냅니다.
- 응답을 처리하기 위해, 우리는 '서버-클라이언트' 유형으로 수신된 방 메시지를 처리하는 리스너도 정의합니다.
- 이 리스너는 서버에서 수신한 수도 이름을 화면에 표시합니다. 여러 수도가 있는 경우, 화면에 쉼표로 구분하여 표시됩니다.



**서버 코드**

```typescript
import { Sandbox, SandboxOptions, SandboxPlayer } from "ZEPETO.Multiplay";
import { HttpService } from "ZEPETO.Multiplay.HttpService";

export default class extends Sandbox {

    onCreate(options: SandboxOptions) {
        // 'client-to-server' 유형의 메시지를 처리합니다.
        this.onMessage("client-to-server", (client, message) => {
            console.log(`클라이언트 ${client.userId}가 요청을 보냈습니다. 메시지: ${message}`);
            this.findCapitalCity(message, client);
        });
    }

    onJoin(client: SandboxPlayer) { }
    onLeave(client: SandboxPlayer, consented?: boolean) { }

    findCapitalCity(countryName: string, client: SandboxPlayer) {
        // countryName을 경로 매개변수로 사용하여 API 요청
        // countryName은 클라이언트가 방 메시지로 보냅니다.
        HttpService.getAsync(`https://restcountries.com/v3.1/name/${countryName}?fields=capital`)
            .then((httpResponse) => {
                if (httpResponse.statusCode != 200) {
                    throw (`API 오류: ${httpResponse.statusCode} ${httpResponse.statusText}`);
                }
                return httpResponse.response;
            })
            .then((response) => {
                let countries = JSON.parse(response);
                if (!Array.isArray(countries) || !countries.length) {
                    throw (`API 오류: 응답 데이터가 유효하지 않습니다.`);
                }
                let country = countries[0];
                if (!country.capital) {
                    throw (`API 오류: 'capital' 필드가 유효하지 않습니다.`);
                }
                // 클라이언트에게 'server-to-client' 유형의 메시지를 보냅니다.
                // 메시지는 수도 이름을 포함하는 객체입니다.
                client.send("server-to-client", {
                    "capitals": country.capital
                });
            })
            .catch((reason) => {
                console.log("API 요청 오류");
                console.log(reason);
            });
    }
}
```

코드 설명:

- 우리는 '클라이언트-서버' 유형의 멀티플레이 룸 메시지를 수신하면 호출하는 리스너를 정의합니다.`findCapitalCity`.
- 우리는 국가 이름을 사용하여 멀티플레이 룸 메시지의 주소를 구성하고 `getAsync` 호출을 합니다.
- 만약 `getAsync` 호출이 성공하면, 응답은 이전 예제와 같이 처리됩니다.
- restcountries API의 응답에서 얻은 수도 이름은 '서버-클라이언트' 유형의 룸 메시지로 클라이언트에 전송됩니다.



### POST 요청

마지막으로, `HttpService.postAsync`를 사용하여 POST 요청 예제를 만들어 보겠습니다.

[postman-echo](https://postman-echo.com/)는 웹 요청에서 수신된 내용을 보여주는 구조화된 응답을 제공하는 서비스로, 클라이언트가 요청을 올바르게 구성했는지 확인하는 데 효과적입니다.

이 예제를 통해 쿼리 매개변수, 요청 본문 및 헤더가 포함된 POST 요청을 설정하고 요청이 올바르게 구성되었는지 확인할 것입니다.

Multiplay를 설정한 다음, World.multiplay/index.ts를 열고 서버 스크립트를 다음과 같이 작성하세요.

```typescript
import { Sandbox, SandboxOptions, SandboxPlayer } from "ZEPETO.Multiplay";
import { HttpContentType, HttpService } from "ZEPETO.Multiplay.HttpService";

export default class extends Sandbox {

    onCreate(options: SandboxOptions) { }
    onJoin(client: SandboxPlayer) { this.echoPost() }
    onLeave(client: SandboxPlayer, consented?: boolean) { }

    echoPost() {
        HttpService.postAsync(
            // API 엔드포인트와 쿼리 매개변수
            "https://postman-echo.com/post?argKey=argValue",
            // JSON 요청 본문
            { "dataKey": "dataValue" },
            // 요청 콘텐츠 유형
            HttpContentType.ApplicationJson,
            // HTTP 헤더
            { "header-key": "header-value" })
            .then((httpResponse) => {
                if (httpResponse.statusCode != 200) {
                    throw (`API 오류: ${httpResponse.statusCode} ${httpResponse.statusText}`);
                }
                return httpResponse.response;
            })
            .then((response) => {
                let parsed = JSON.parse(response);
                console.log(`쿼리 매개변수: argKey:${parsed.args.argKey}`);
                console.log(`요청 본문: dataKey:${parsed.data.dataKey}`);
                console.log(`요청 헤더: header-key:${parsed.headers["header-key"]}`)
            })
            .catch((reason) => {
                console.log("API 요청 오류");
                console.log(reason);
            });
    }
}
```

코드 설명:

- 클라이언트가 멀티플레이 룸에 연결될 때, `echoPost` 함수가 `onJoin` 트리거에서 호출됩니다.
- &#x20;`postAsync` 요청을 만들 때:
  - 첫 번째 매개변수에서 쿼리 매개변수를 포함한 URL 문자열을 구성합니다.
  - 두 번째 매개변수에서 요청 본문 내용을 설정합니다.
  - 네 번째 매개변수에서 요청 헤더를 구성합니다.
  - 요청 본문이 JSON 형식임을 지정하기 위해 세 번째 매개변수에 'application/json' Content-Type을 설정합니다.
- 만약 `postAsync` 호출이 성공하면, 우리는 `httpResponse` 객체에 접근할 수 있습니다.`그 후` 콜백에서.
- 우리는 `httpResponse.response`를 파싱하여 API 응답을 JSON 형식에서 객체로 변환합니다.
- API 응답의 구조를 참조하여, 우리는 콘솔 출력을 사용하여 HTTP 요청의 쿼리 매개변수, 요청 본문 및 요청 헤더가 올바르게 구성되었는지 확인합니다.

Unity 에디터에서 멀티플레이 서버를 실행하고 장면을 재생하면 콘솔에 다음과 같은 내용이 표시됩니다.

```text
쿼리 매개변수: argKey:argValue
요청 본문: dataKey:dataValue
요청 헤더: header-key:header-value
```

