Bu örnekte, HTTP işleyicilerinin kullanımını standart hale getirmesi için bir arayüz oluşturacağız. İşleyiciler bir yanıt nesnesi ve hata döndürür. API çağrıları için hata ve yanıt işlemeyi basitleştirmeye yardımcı olacaktır. Örnek basit bit seviyede tutuldu, ancak onu geliştirmelisiniz, o nedenle olduğu gibi kabul etmeyin.


main.go


package main

import (
"log"

"random/http"
"random/users"
)

func main() {
rtr := http.NewRouter()
rtr.Add("/api/v1/users", users.Handler)

srv := http.NewServer(rtr.Handler, ":3000")
log.Fatal(srv.ListenAndServe())
}

http/server.go


package http

import (
"net/http"
)

func NewServer(handler http.Handler, address string) *http.Server {
return &http.Server{
Handler: handler,
Addr: address,
}
}


http/router.go


package http

import "net/http"

type Router struct {
Handler *http.ServeMux
}

func NewRouter() *Router {
return &Router{
Handler: http.NewServeMux(),
}
}

func (r *Router) Add(path string, hnd handler) {
r.Handler.Handle(path, handler(hnd))
}

http/handler.go


package http

import (
"errors"
"fmt"
"net/http"
)

type handler func(http.ResponseWriter, *http.Request) (*Response, error)

func (h handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
res, err := h(w, r)
if err == nil {
h.success(w, res)
return
}

h.error(w, err)
}

func (h handler) success(w http.ResponseWriter, res *Response) {
if err := res.Write(w); err != nil {
log.Println(err)
}
}

func (h handler) error(w http.ResponseWriter, err error) {
var e Error
if errors.As(err, &e) {
res := &Response{
Status: e.Status,
Code: e.Code,
Message: e.Message,
}
if err := res.Write(w); err != nil {
log.Println(err)
}

return
}

log.Println(err)

res := &Response{
Status: http.StatusInternalServerError,
Code: "internal_error",
Message: "An internal error has occurred",
}
if err := res.Write(w); err != nil {
log.Println(err)
}
}

http/error.go


package http

type Error struct {
Status int
Code string
Message string
}

func (e Error) Error() string {
return e.Message
}

http/response.go


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

type Response struct {
Status int `json:"-"`
Headers map[string]string `json:"-"`

Data any `json:"data,omitempty"`
Meta any `json:"meta,omitempty"`

Code string `json:"code,omitempty"`
Message string `json:"message,omitempty"`
Errors map[string]string `json:"errors,omitempty"`
}

func (r *Response) Write(w http.ResponseWriter) error {
if r.Status < 200 || r.Status > 599 {
return fmt.Errorf("response: invalid status code: %d", r.Status)
}

for k, v := range r.Headers {
w.Header().Add(k, v)
}

if (r.Status >= 100 && r.Status <= 199) || r.Status == 204 || r.Status == 304 {
w.WriteHeader(r.Status)

return nil
}

bdy, err := json.Marshal(r)
if err != nil {
return fmt.Errorf("response: marshal body: %s", err.Error())
}

w.Header().Set("Content-Type", "application/json; charset=utf-8")
w.WriteHeader(r.Status)

if _, err := w.Write(bdy); err != nil {
return fmt.Errorf("response: write body: %s", err.Error())
}

return nil
}

domain/error.go


package domain

import (
"net/http"

httppkg "random/http"
)

var (
ErrInternalError = httppkg.Error{
Status: http.StatusInternalServerError,
Code: "internal_error",
Message: "An internal error has occurred",
}
ErrInvalidRequest = httppkg.Error{
Status: http.StatusBadRequest,
Code: "invalid_request",
Message: "Request is invalid",
}
ErrResourceConflict = httppkg.Error{
Status: http.StatusConflict,
Code: "resource_conflict",
Message: "A resource conflict has been detected",
}
ErrResourceNotFound = httppkg.Error{
Status: http.StatusNotFound,
Code: "resource_not_found",
Message: "Resource is not found",
}
)

users/api.go


package users

import (
"errors"
"net/http"

"random/domain"

httppkg "random/http"
)

func Handler(w http.ResponseWriter, r *http.Request) (*httppkg.Response, error) {
id := r.Header.Get("id")
if id == "" {
return nil, errors.New("not known error")
}
if id == "0" {
return nil, domain.ErrResourceNotFound
}
if id == "1" {
return nil, domain.ErrResourceConflict
}
if id == "3" {
return &httppkg.Response{
Status: domain.ErrInvalidRequest.Status,
Code: domain.ErrInvalidRequest.Code,
Message: domain.ErrInvalidRequest.Message,
Errors: map[string]string{"id": "Invalid digit"},
}, nil
}

return &httppkg.Response{
Status: http.StatusOK,
Data: "good job",
Meta: "paging",
}, nil
}