본문 바로가기
개발/나머지

golang http request template

by 앗가 2022. 10. 9.
728x90
반응형

golang으로 http request를 보내거나 개발형, 코딩 테스트(누가 근데 golang쓰긴 하겠냐마는...)를 준비할 때 쓸 수 있는 템플릿입니다. 

golang으로 리퀘스트를 보낼 때 json 직렬화, byte string 변환과 같은 과정이 번거로워서 간단하게 구현해보았습니다. 

CallAPI함수에 response 타입을 넣고 method, uri, header, body를 명시하면 결과를 리턴하도록 간단하게 net/http 기반으로 외부 라이브러리 없이 구현했습니다. 

package main

import (
	"bytes"
	"encoding/json"
	"io"
	"net/http"
)

const (
	GET    = "GET"
	POST   = "POST"
	PUT    = "PUT"
	DELETE = "DELETE"
)

// API 호출 함수
// T: response type
func CallAPI[T any](method string, uri string, header map[string]string, body interface{}, client *http.Client) (T, error) {
	var result T
	buff := _createBody(body)

	req, err := http.NewRequest(method, uri, buff)
	if err != nil {
		return result, err
	}

	for key, value := range header {
		req.Header.Add(key, value)
	}

	err = _send(client, req, &result)
	if err != nil {
		return result, err
	}

	return result, nil
}

// request를 받아 클라이언트에 전달하고 결과를 target에 받아옴
func _send[T any](client *http.Client, request *http.Request, target *T) error {
	res, err := client.Do(request)
	if err != nil {
		return err
	}

	defer res.Body.Close()
	body, err := io.ReadAll(res.Body)
	if err != nil {
		return err
	}

	json.Unmarshal(body, target)

	return nil
}

// post, put 형식의 body를 만들기 위해 구조체를 buffer 형태로 변환
func _createBody(source interface{}) *bytes.Buffer {
	pbytes, _ := json.Marshal(source)
	buff := bytes.NewBuffer(pbytes)
	return buff
}

 

전체 예시. API 사이트는 https://jsonplaceholder.typicode.com/guide를 참조했습니다.

package main

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io"
	"net/http"
)

const (
	BASE_URL = "https://jsonplaceholder.typicode.com" // API 사용 가이드: https://jsonplaceholder.typicode.com/guide
	GET      = "GET"
	POST     = "POST"
	PUT      = "PUT"
	DELETE   = "DELETE"
)

// API 호출 함수
// T: response type
func CallAPI[T any](method string, uri string, header map[string]string, body interface{}, client *http.Client) (T, error) {
	var result T
	buff := _createBody(body)

	req, err := http.NewRequest(method, uri, buff)
	if err != nil {
		return result, err
	}

	for key, value := range header {
		req.Header.Add(key, value)
	}

	err = _send(client, req, &result)
	if err != nil {
		return result, err
	}

	return result, nil
}

// request를 받아 클라이언트에 전달하고 결과를 target에 받아옴
func _send[T any](client *http.Client, request *http.Request, target *T) error {
	res, err := client.Do(request)
	if err != nil {
		return err
	}

	defer res.Body.Close()
	body, err := io.ReadAll(res.Body)
	if err != nil {
		return err
	}

	json.Unmarshal(body, target)

	return nil
}

// post, put 형식의 body를 만들기 위해 구조체를 buffer 형태로 변환
func _createBody(source interface{}) *bytes.Buffer {
	pbytes, _ := json.Marshal(source)
	buff := bytes.NewBuffer(pbytes)
	return buff
}

type Resource struct {
	Id     int    `json:"id"`
	Title  string `json:"title"`
	Body   string `json:"body"`
	UserId int    `json:"userId"`
}

func main() {
	client := &http.Client{}

	// get
	getResourceUri := BASE_URL + "/posts/1"
	getResourceRes, err := CallAPI[Resource](GET, getResourceUri, nil, nil, client)
	if err != nil {
		fmt.Println("error: ", err)
		return
	}
	fmt.Println(getResourceUri, getResourceRes)

	// post
	postResourceUri := BASE_URL + "/posts"
	postResourceHeader := map[string]string{"Content-Type": "application/json; charset=UTF-8"}
	postResourceBody := Resource{Title: "foo", Body: "bar", UserId: 1}
	postResourceRes, err := CallAPI[Resource](POST, postResourceUri, postResourceHeader, postResourceBody, client)
	if err != nil {
		fmt.Println("error: ", err)
		return
	}
	fmt.Println(postResourceUri, postResourceRes)

	// put
	putResourceUri := BASE_URL + "/posts/1"
	putResourceHeader := map[string]string{"Content-Type": "application/json; charset=UTF-8"}
	putResourceBody := Resource{Id: 1, Title: "foo", Body: "bar", UserId: 1}
	putResourceRes, err := CallAPI[Resource](PUT, putResourceUri, putResourceHeader, putResourceBody, client)
	if err != nil {
		fmt.Println("error: ", err)
		return
	}
	fmt.Println(putResourceUri, putResourceRes)

	// delete
	deleteResourceUri := BASE_URL + "/posts/1"
	CallAPI[Resource](DELETE, deleteResourceUri, nil, nil, client)
}

 

728x90

댓글