실전 REST API 통신하기 (Gemini API, Pixabay API)

 


RestApiBinder.cs 클래스입니다


using System.Collections.Generic;
using System.Text;
using UnityEngine;
using UnityEngine.Networking;

public class RestApiBinder
{
    public async Awaitable<DownloadHandler> HttpAsync(string url, string methodCapital, Dictionary<string, string> headers, string body) 
    {
        if (body != null)
        {
            byte[] bodyData = Encoding.UTF8.GetBytes(body);
            return await HttpByteAsync(url, methodCapital, headers, bodyData);
        }
        else
        {
            return await HttpByteAsync(url, methodCapital, headers, null);
        }
    }

    public async Awaitable<DownloadHandler> HttpByteAsync(string url, string methodCapital, Dictionary<string, string> headers, byte[] body) 
    {
        UnityWebRequest request = new UnityWebRequest(url, methodCapital, new DownloadHandlerBuffer(), new UploadHandlerRaw(body));
        if (headers != null)
        {
            foreach (var header in headers)
            {
                request.SetRequestHeader(header.Key, header.Value);
            }
        }
        
        await request.SendWebRequest();
        if (request.result != UnityWebRequest.Result.Success)
        {
            Debug.LogError($"Error {request.responseCode}: {request.error}");
            return null;
        }
        return request.downloadHandler;
    }
}








Test.cs 클래스입니다


using System.Collections.Generic;
using System.IO;
using System.Text;
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.Networking;
using UnityEngine.Video;
using Newtonsoft.Json.Linq;

public class Test : MonoBehaviour
{
    #region APIKey
    const string GEMINI_API_KEY = "YOUR_GEMINI_API_KEY";
    const string PIXABAY_API_KEY = "YOUR_PIXABAY_API_KEY";
    #endregion

    RestApiBinder restApiBinder = new();
    [SerializeField] Texture2D sampleTexture;
    [SerializeField] RawImage rawImage;
    [SerializeField] VideoPlayer videoPlayer;



    [ContextMenu("GetGeminiText")]
    async Awaitable GetGeminiText() 
    {
        string url = "https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash:generateContent";
        var headers = new Dictionary<string, string> 
        {
            { "x-goog-api-key", GEMINI_API_KEY },
            { "Content-Type", "application/json" }
        };
        string myQuestion = "한국의 수도는 뭐야?";
        string body = $@"{{
            ""contents"": [
            {{
                ""parts"": [
                {{
                    ""text"": ""{myQuestion}""
                }}
                ]
            }}
            ]
        }}";
        DownloadHandler downloadHandler = await restApiBinder.HttpAsync(url, "POST", headers, body);
        JObject json = JObject.Parse(downloadHandler.text);
        string responseText = json["candidates"][0]["content"]["parts"][0]["text"].ToString();

        print(responseText);
    }

    [ContextMenu("GetGeminiMultimodal")]
    async Awaitable GetGeminiMultimodal() 
    {
        string url = "https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash:generateContent";
        var headers = new Dictionary<string, string> 
        {
            { "x-goog-api-key", GEMINI_API_KEY },
            { "Content-Type", "application/json" }
        };
        Texture2D uncompressTexture = UncompressTexture2D(sampleTexture);
        string base64Image = System.Convert.ToBase64String(uncompressTexture.EncodeToJPG());
        string body = $@"{{
            ""contents"": [
                {{
                ""parts"": [
                    {{
                    ""text"": ""이 그림에 대해 설명해줘.""
                    }},
                    {{
                    ""inline_data"": {{
                        ""mime_type"": ""image/jpeg"",
                        ""data"": ""{base64Image}""
                    }}
                    }}
                ]
                }}
            ]
        }}";
        DownloadHandler downloadHandler = await restApiBinder.HttpAsync(url, "POST", headers, body);
        JObject json = JObject.Parse(downloadHandler.text);
        string responseText = json["candidates"][0]["content"]["parts"][0]["text"].ToString();

        print(responseText);
    }

    [ContextMenu("GetGeminiChat")]
    async Awaitable GetGeminiChat() 
    {
        string url = "https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash:generateContent";
        var headers = new Dictionary<string, string> 
        {
            { "x-goog-api-key", GEMINI_API_KEY },
            { "Content-Type", "application/json" }
        };
        string body = @"{
            ""contents"": [
            {
                ""role"": ""user"",
                ""parts"": [
                {
                    ""text"": ""Hello""
                }
                ]
            },
            {
                ""role"": ""model"",
                ""parts"": [
                {
                    ""text"": ""Great to meet you. What would you like to know?""
                }
                ]
            },
            {
                ""role"": ""user"",
                ""parts"": [
                {
                    ""text"": ""I have two dogs in my house. How many paws are in my house?""
                }
                ]
            }
            ]
        }";
        DownloadHandler downloadHandler = await restApiBinder.HttpAsync(url, "POST", headers, body);
        JObject json = JObject.Parse(downloadHandler.text);
        string responseText = json["candidates"][0]["content"]["parts"][0]["text"].ToString();

        print(responseText);
    }

    [ContextMenu("GetGeminiAudio")]
    async Awaitable GetGeminiAudio() 
    {
        string url = "https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash-preview-tts:generateContent";
        var headers = new Dictionary<string, string> 
        {
            { "x-goog-api-key", GEMINI_API_KEY },
            { "Content-Type", "application/json" }
        };
        string body = @"{
            ""contents"": [{
            ""parts"":[{
                ""text"": ""Say cheerfully: Have a wonderful day!""
            }]
            }],
            ""generationConfig"": {
            ""responseModalities"": [""AUDIO""],
            ""speechConfig"": {
                ""voiceConfig"": {
                ""prebuiltVoiceConfig"": {
                    ""voiceName"": ""Zephyr""
                }
                }
            }
            },
            ""model"": ""gemini-2.5-flash-preview-tts"",
        }";
        DownloadHandler downloadHandler = await restApiBinder.HttpAsync(url, "POST", headers, body);
        JObject json = JObject.Parse(downloadHandler.text);
        string base64Data = json["candidates"][0]["content"]["parts"][0]["inlineData"]["data"].ToString();
        byte[] audioBytes = System.Convert.FromBase64String(base64Data);

        string filePath = Path.Combine(Application.persistentDataPath, "gemini_audio.wav");
        SaveFilePcmToWav(audioBytes, filePath);
        print($"Audio saved: {filePath}");
    }

    [ContextMenu("GetGeminiImage")]
    async Awaitable GetGeminiImage() 
    {
        string url = "https://generativelanguage.googleapis.com/v1beta/models/gemini-2.0-flash-preview-image-generation:generateContent";
        var headers = new Dictionary<string, string> 
        {
            { "x-goog-api-key", GEMINI_API_KEY },
            { "Content-Type", "application/json" }
        };
        string body = @"{
            ""contents"": [{
            ""parts"": [
                {""text"": ""Hi, can you create a 3d rendered image of a pig with wings and a top hat flying over a happy futuristic scifi city with lots of greenery?""}
            ]
            }],
            ""generationConfig"":{""responseModalities"":[""TEXT"",""IMAGE""]}
        }";
        DownloadHandler downloadHandler = await restApiBinder.HttpAsync(url, "POST", headers, body);
        JObject json = JObject.Parse(downloadHandler.text);
        string base64Data = json["candidates"][0]["content"]["parts"][1]["inlineData"]["data"].ToString();
        byte[] imageBytes = System.Convert.FromBase64String(base64Data);
        string filePath = Path.Combine(Application.persistentDataPath, "gemini_image.png");
        SaveFileImageAndShow(imageBytes, filePath, rawImage);

        print($"Image saved: {filePath}");
    }

    [ContextMenu("GetPixabayImage")]
    async Awaitable GetPixabayImage() 
    {
        string url = $"https://pixabay.com/api?key={PIXABAY_API_KEY}&q=yellow+flower";
        var headers = new Dictionary<string, string> 
        {
            { "Content-Type", "application/json" }
        };
        DownloadHandler downloadHandler = await restApiBinder.HttpAsync(url, "GET", headers, null);
        JObject json = JObject.Parse(downloadHandler.text);
        string imageUrl = json["hits"][0]["largeImageURL"].ToString();

        downloadHandler = await restApiBinder.HttpAsync(imageUrl, "GET", null, null);
        string filePath = Path.Combine(Application.persistentDataPath, "pixabay_image.jpg");
        SaveFileImageAndShow(downloadHandler.data, filePath, rawImage);

        print($"Image saved: {filePath}");
    }

    [ContextMenu("GetPixabayVideo")]
    async Awaitable GetPixabayVideo() 
    {
        string url = $"https://pixabay.com/api/videos?key={PIXABAY_API_KEY}&q=yellow+flower";
        var headers = new Dictionary<string, string> 
        {
            { "Content-Type", "application/json" }
        };
        DownloadHandler downloadHandler = await restApiBinder.HttpAsync(url, "GET", headers, null);
        JObject json = JObject.Parse(downloadHandler.text);

        string videoUrl = json["hits"][0]["videos"]["large"]["url"].ToString();
        downloadHandler = await restApiBinder.HttpAsync(videoUrl, "GET", null, null);
        string filePath = Path.Combine(Application.persistentDataPath, "pixabay_video.mp4");
        SaveFileVideoAndShow(downloadHandler.data, filePath, videoPlayer);

        print($"Video saved: {filePath}");
    }


    Texture2D UncompressTexture2D(Texture2D texture) 
    {
        Texture2D readableTexture = new Texture2D(texture.width, texture.height, TextureFormat.RGBA32, false);
        readableTexture.SetPixels(texture.GetPixels());
        readableTexture.Apply();
        return readableTexture;
    }

    void SaveFilePcmToWav(byte[] pcmData, string filePath, int sampleRate = 24000, int channels = 1, int bitsPerSample = 16)
    {
        using (var fileStream = new FileStream(filePath, FileMode.Create))
        using (var writer = new BinaryWriter(fileStream))
        {
            writer.Write(Encoding.ASCII.GetBytes("RIFF"));
            writer.Write(36 + pcmData.Length);
            writer.Write(Encoding.ASCII.GetBytes("WAVE"));
            
            writer.Write(Encoding.ASCII.GetBytes("fmt "));
            writer.Write(16);
            writer.Write((short)1);
            writer.Write((short)channels);
            writer.Write(sampleRate);
            writer.Write(sampleRate * channels * bitsPerSample / 8);
            writer.Write((short)(channels * bitsPerSample / 8));
            writer.Write((short)bitsPerSample);

            writer.Write(Encoding.ASCII.GetBytes("data"));
            writer.Write(pcmData.Length);
            writer.Write(pcmData);
        }
    }

    void SaveFileImageAndShow(byte[] imageData, string filePath, RawImage rawImage) 
    {
        File.WriteAllBytes(filePath, imageData);
        Texture2D texture = new Texture2D(1, 1);
        texture.LoadImage(imageData);
        rawImage.texture = texture;
        float aspectRatio = (float)texture.width / (float)texture.height;
        rawImage.GetComponent<AspectRatioFitter>().aspectRatio = aspectRatio;
    }

    void SaveFileVideoAndShow(byte[] videoData, string filePath, VideoPlayer videoPlayer) 
    {
        File.WriteAllBytes(filePath, videoData);
        videoPlayer.source = VideoSource.Url;
        videoPlayer.url = filePath;
        videoPlayer.Play();
    }
}