Home >Backend Development >Golang >golang implements api gateway
With the popularity of microservice architecture and the increase in the number of services, in order to improve the security, reliability and scalability of services, API gateway technology has emerged as the times require. Today we will talk about how to use Golang to write an API gateway.
1. Why choose to use Golang
2. API Gateway Design Ideas
Before designing an API gateway, we need to know what an API gateway needs to do. Generally speaking, an API gateway needs to do the following:
According to the above ideas, we design a simple API gateway, which mainly includes the following components:
3. Implement API Gateway
Next we will implement the above components one by one.
1. Router
In order to implement a router, we first need to define a routing item:
type Route struct { Path string Method string Handler http.Handler }
Route contains a Path for storing routing paths and a Method for storing The method type of the request, a Handler is used to store the method for processing the request.
Next we define a Router structure to store the routing list and route the request to the corresponding processor:
type Router struct { routes []*Route } func (r *Router) HandleFunc(path string, method string, handlerFunc http.HandlerFunc) { r.routes = append(r.routes, &Route{Path: path, Method: method, Handler: handlerFunc}) } func (r *Router) ServeHTTP(w http.ResponseWriter, req *http.Request) { for _, route := range r.routes { if route.Path == req.URL.Path && route.Method == req.Method { route.Handler.ServeHTTP(w, req) return } } http.Error(w, "404 not found", http.StatusNotFound) }
In the code, we provide two methods for registration Request processing method and routing. HandleFunc should be similar to http.HandleFunc. It will bind the routing address, request method and request processing method to an object. ServeHTTP will look for a matching route after receiving the request. If found, it will The request is forwarded to the corresponding processing method, and 404 is returned if not found.
Next write a simple processing method implementation:
func main() { router := &Router{} router.HandleFunc("/hello", "GET", func(w http.ResponseWriter, r *http.Request) { fmt.Fprint(w, "hello") }) http.ListenAndServe(":8080", router) }
This processing method will return hello for any GET request with the address /hello.
2. Filter
WAF (Web Application Firewall) is a commonly used filter used to protect web applications from various attacks. We can filter requests based on request method, request headers, request parameters and other information. Here, we use request headers as filters. If the request header contains a specific tag, it is passed, otherwise it is considered an invalid request.
To implement filters using Golang, we need to write a middleware that checks whether each request contains a specific tag. If it is included, continue to pass the request down, otherwise an error will be returned. We can use gorilla/mux to implement middleware.
type FilterMiddleware struct { next http.Handler } func (f *FilterMiddleware) ServeHTTP(w http.ResponseWriter, r *http.Request) { if r.Header.Get("X-Auth-Token") != "magic" { http.Error(w, "Unauthorized request", http.StatusUnauthorized) return } f.next.ServeHTTP(w, r) } func (r *Router) RegisterMiddleware(middleware func(http.Handler) http.Handler) { handler := http.Handler(r) for _, m := range middlewareFunctions { handler = m(handler) } r.Handler = handler }
In the code, FilterMiddleware will check whether the request header contains the "X-Auth-Token" tag. If it does, the request will be passed down, otherwise an unauthorized error will be returned. We also define a RegisterMiddleware function for registering middleware.
3. Load balancing
Load balancing is one of the most important components in the API gateway. It can distribute requests to different service instances on the backend. We can use polling, random and other algorithms to achieve this.
Here we use a simple polling algorithm for load balancing. We can pick the address of the next server from a pool and forward the request to that server.
type LoadBalancer struct { Pool []string Next int } func (l *LoadBalancer) Pick() string { if l.Next >= len(l.Pool) { l.Next = 0 } host := l.Pool[l.Next] l.Next++ return host } func (r *Router) HandleFunc(path string, method string, handlerFunc http.HandlerFunc) { l := &LoadBalancer{ Pool: []string{"http://127.0.0.1:8081", "http://127.0.0.1:8082"}, Next: 0, } handler := http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { url, err := url.Parse(l.Pick()) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } proxy := httputil.NewSingleHostReverseProxy(url) proxy.ServeHTTP(w, req) }) r.routes = append(r.routes, &Route{Path: path, Method: method, Handler: handler}) }
In the code, we created a simple load balancing structure that contains the server pool and the location of the next server to be selected. The pick() method will select a server address based on the length of the server pool. In HandleFunc, we use the Round Robin algorithm to forward requests to different servers.
4. Caching
Cache can improve the performance of the system and also reduce the number of requests to the backend. In the API gateway, we can embed the cache in the request response process and directly return the request in the cache that can quickly provide a response.
type Cache struct { data map[string] []byte mutex sync.Mutex } func (c *Cache) Get(key string) []byte { c.mutex.Lock() defer c.mutex.Unlock() if value, ok := c.data[key]; ok { return value } return nil } func (c *Cache) Set(key string, value []byte) { c.mutex.Lock() defer c.mutex.Unlock() c.data[key] = value } func (r *Router) HandleFunc(path string, method string, handlerFunc http.HandlerFunc) { cache := &Cache{ data: make(map[string][]byte), } handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { key := r.URL.String() if data := cache.Get(key); data != nil { w.Write(data) return } buffer := &bytes.Buffer{} proxy := httputil.NewSingleHostReverseProxy(r.URL) proxy.ServeHTTP(buffer, r) cache.Set(key, buffer.Bytes()) w.Write(buffer.Bytes()) }) r.routes = append(r.routes, &Route{Path: path, Method: method, Handler: handler}) }
In the code, we created a cache structure and cached the request in HandleFunc. If the same request exists in the cache, we can return the result directly from the cache, thus reducing the number of requests to the backend.
5. Monitoring
Monitoring can help us better understand the running status of the system and grasp the response speed of the current system.
使用Prometheus来实现API网关的监控。我们只需记录下来每一个请求的响应时间、状态码及信息,然后将所有的数据发送到Prometheus。
var ( apiRequestsDuration = prometheus.NewHistogramVec(prometheus.HistogramOpts{ Name: "api_request_duration_seconds", Help: "The API request duration distribution", Buckets: prometheus.DefBuckets, }, []string{"status"}) ) type MetricMiddleware struct { next http.Handler } func (m *MetricMiddleware) ServeHTTP(w http.ResponseWriter, r *http.Request) { start := time.Now() ww := httptest.NewRecorder() m.next.ServeHTTP(ww, r) duration := time.Since(start) apiRequestsDuration.WithLabelValues(strconv.Itoa(ww.Code)).Observe(duration.Seconds()) for key, values := range ww.Header() { for _, value := range values { w.Header().Add(key, value) } } w.WriteHeader(ww.Code) body := ww.Body.Bytes() w.Write(body) } func main() { router := &Router{} router.HandleFunc("/hello", "GET", func(w http.ResponseWriter, r *http.Request) { fmt.Fprint(w, "hello") }) logger := NewLoggerMiddleware(router) http.Handle("/metrics", prometheus.Handler()) http.Handle("/", logger) service := ":8080" log.Printf("Server starting on %v ", service) log.Fatal(http.ListenAndServe(service, nil)) }
在代码中,我们定义了一个MetricMiddleware并在请求结束后统计相关时间数据,最后通过Prometheus将数据输出到Metrics监控系统中。我们还通过http.Handle将Prometheus绑定在“/metrics”路径上,方便查询。
结束语
至此,我们用Golang实现了一个简单的API网关。在实际使用中,我们还可以添加更多的功能,如熔断、限流、容错等,来提高其安全性、稳定性和可用性。
The above is the detailed content of golang implements api gateway. For more information, please follow other related articles on the PHP Chinese website!