如何使用golang实现一个api网关(代码片段)

一个兴趣使然的程序员 一个兴趣使然的程序员     2022-12-25     201

关键词:

你是否也存在过这样的需求,想要公开一个接口到网络上。但是还得加点权限,否则被人乱调用就不好了。这个权限验证的过程,最好越简单越好,可能只是对比两个字符串相等就够了。一般情况下我们遇到这种需要,就是在函数实现或者添加一个全局的拦截器就够了。但是还是需要自己来写那部分虽然简单但是很啰嗦的代码。那么存不存在一种方式,让我只管写我的代码就完了,鉴权的事情交给其他人来做呢?

OpenAPI 一般情况下,就是允许企业内部提供对外接口的项目。你只管写你的接口,然后,在我这里注册一下,我来负责你的调用权限判定,如果他没有权限,我就告诉他没有权限,如果他存在权限,我就转调一下你的接口,然后把结果返回给他。其实情景是相似的,我们可以把这段需求抽象,然后做一个配置文件版的开放接口。

想做这件事情,其实Golang是一个非常不错的选择,首先,Golang对于这种转调的操作非常友好,甚至于,Golang语言本身就提供了一个反向代理的实现,我们可以直接使用Golang的原始框架就完全够用。
在简单分析一下我们的需求,其实很简单,监听的某一段Path之后,先判断有没有权限,没有权限,直接回写结果,有权限交给反向代理来实现,轻松方便。既然是这样,我们需要定义一下,路径转发的规则。

比如说我们尝试给这个接口添加一个,当然这只是其中一个接口,我们应该要支持好多个接口

http://api.qingyunke.com/api.php?key=free&appid=0&msg=hello%20world.

在他进入到我们的系统中的时候看上去可能是这样的。
http://localhost:5000/jiqiren/api.php?key=free&appid=0&msg=hello%20world.

所以,在我们的配置里边也应该是支持多个节点配置的。


  "upstreams": [
    
      "upstream": "http://api.qingyunke.com",
      "path": "/jiqieren/",
      "trim_path": true,
      "is_auth": true
    
  ],
  ...

upstreams:上游服务器

upstream:上游服务器地址

path:路径,如果以斜线结尾的话代表拦截所有以 /jiqiren/开头的链接

trim_path:剔除路径,因为上游服务器中其实并不包含 /jiqiren/ 这段的,所以要踢掉这块

is_auth:是否是授权链接

 

其实至此的上游的链接已经配置好了,下面我们来配置一下授权相关的配置。现在我实现的这个版本里边允许同时存在多个授权类型。满足任何一个即可进行接口的调用。我们先简单配置一个bearer的版本。


 ...
  "auth_items": 
    "Bearer": 
      "oauth_type": "BearerConfig",
      "configs": 
        "file": "bearer.json"
      
    
  

Bearer 对应的Model的意思是说,要引用配置文件的类型,对应的文件是 bearer.json

对应的文件内容如下


  "GnPIymAqtPEodx2di0cS9o1GP9QEM2N2-Ur_5ggvANwSKRewH2DLmw": 
    "interfaces": [
      "/jiqieren/api.php"
    ],
    "headers": 
      "TenantId": "100860"
    
  

其实就是一个Key对应了他能调用那些接口,还有他给上游服务器传递那些信息。因为Token的其实一般不光是能不能调用,同时他还代表了某一个服务,或者说某一个使用者,对应的,我们可以将这些信息,放到请求头中传递给上游服务器。就可以做到虽然上游服务器,并不知道Token但是上游服务器知道谁能够调用它。

下面我们来说一下这个项目是如何实现的。其实,整个功能简单的描述起来就是一个带了Token解析、鉴权的反向代理。但是本质上他还是一个反向代理,我们可以直接使用Golang自带的反向代理。

核心代码如下。

package main

import (
    "./Configs"
    "./Server"
    "encoding/json"
    "flag"
    "fmt"
    "io/ioutil"
    "log"
    "net/http"
    "net/http/httputil"
    "net/url"
    "os"
    "strings"
)

func main() 
    var port int
    var config string

    flag.IntVar(&port, "port", 80, "server port")
    flag.StringVar(&config, "config", "", "mapping config")

    flag.Parse()

    if config == "" 
        log.Fatal("not found config")
    

    if fileExist(config) == false 
        log.Fatal("not found config file")
    

    data, err := ioutil.ReadFile(config)
    if err != nil 
        log.Fatal(err)
    

    var configInstance Configs.Config
    err = json.Unmarshal(data, &configInstance)
    if err != nil 
        log.Fatal(err)
    

    auths := make(map[string]Server.IAuthInterface)

    if configInstance.AuthItems != nil 
        for name, configItem := range configInstance.AuthItems 
            auth_item := Server.GetAuthFactoryInstance().CreateAuthInstance(configItem.OAuthType)

            if auth_item == nil 
                continue
            

            auth_item.InitWithConfig(configItem.Configs)
            auths[strings.ToLower(name)] = auth_item
            log.Println(name, configItem)
        
    

    for i := 0; i < len(configInstance.Upstreams); i++ 
        up := configInstance.Upstreams[i]
        u, err := url.Parse(up.Upstream)

        log.Printf("%s => %s
", up.Application, up.Upstream)

        if err != nil 
            log.Fatal(err)
        

        rp := httputil.NewSingleHostReverseProxy(u)

        http.HandleFunc(up.Application, func(writer http.ResponseWriter, request *http.Request) 
            o_path := request.URL.Path

            if up.UpHost != "" 
                request.Host = up.UpHost
             else 
                request.Host = u.Host
            

            if up.TrimApplication 
                request.URL.Path = strings.TrimPrefix(request.URL.Path, up.Application)
            

            if up.IsAuth 
                auth_value := request.Header.Get("Authorization")
                if auth_value == "" 
                    writeUnAuthorized(writer)
                    return
                

                sp_index := strings.Index(auth_value, " ")
                auth_type := auth_value[:sp_index]
                auth_token := auth_value[sp_index+1:]

                if auth_instance, ok := auths[strings.ToLower(auth_type)]; ok 
                    err, headers := auth_instance.GetAuthInfo(auth_token, o_path)
                    if err != nil 
                        writeUnAuthorized(writer)
                     else 
                        if headers != nil 
                            for k, v := range headers 
                                request.Header.Add(k, v)
                            
                        
                        rp.ServeHTTP(writer, request)
                    
                 else 
                    writeUnsupportedAuthType(writer)
                
             else 
                rp.ServeHTTP(writer, request)
            
        )
    

    log.Printf("http server start on :%d
", port)
    http.ListenAndServe(fmt.Sprintf(":%d", port), nil)
    log.Println("finsh")


func writeUnsupportedAuthType(writer http.ResponseWriter) () 
    writer.Header().Add("Content-Type", "Application/json")
    writer.WriteHeader(http.StatusBadRequest)
    writer.Write([]byte(""status":"unsupported authorization""))


func writeUnAuthorized(writer http.ResponseWriter) 
    writer.Header().Add("Content-Type", "Application/json")
    writer.WriteHeader(http.StatusUnauthorized)
    writer.Write([]byte(""status":"un-authorized""))


func fileExist(filename string) bool 
    _, err := os.Stat(filename)
    return err == nil || os.IsExist(err)

最核心的代码不足150行,简单点说就是,在反向代理中间加上了鉴权的逻辑。当然鉴权的逻辑,我做了一层抽象,现在是通过配置文件来进行动态修改的。

package Server

import (
    "log"
    "strings"
)

type IAuthInterface interface 
    GetAuthInfo(token string, url string) (err error, headers map[string]string)
    InitWithConfig(config map[string]string)


type AuthFactory struct 


var auth_factory_instance AuthFactory

func init() 
    auth_factory_instance = AuthFactory


func GetAuthFactoryInstance() *AuthFactory 
    return &auth_factory_instance


func (this *AuthFactory) CreateAuthInstance(t string) IAuthInterface 
    if strings.ToLower(t) == "bearer" 
        return &BeareAuth
    

    if strings.ToLower(t) == "bearerconfig" 
        return &BearerConfigAuth
    

    log.Fatalf("%s 是不支持的类型 
", t)
    return nil
package Server

import (
    "encoding/json"
    "errors"
    "io/ioutil"
    "log"
)

type BearerConfigItem struct 
    Headers    map[string]string `json:"headers"`
    Interfaces []string          `json:"interfaces"`


type BearerConfigAuth struct 
    Configs map[string]*BearerConfigItem // token =》 config item


func (this *BearerConfigAuth) GetAuthInfo(token string, url string) (err error, headers map[string]string) 
    configItem := this.Configs[token]
    if configItem == nil 
        err = errors.New("not found token")
        return
    

    if IndexOf(configItem.Interfaces, url) == -1 
        err = errors.New("un-authorized")
        return
    

    headers = make(map[string]string)
    for k, v := range configItem.Headers 
        headers[k] = v
    

    return


func (this *BearerConfigAuth) InitWithConfig(config map[string]string) 
    cFile := config["file"]
    if cFile == "" 
        return
    

    data, err := ioutil.ReadFile(cFile)
    if err != nil 
        log.Panic(err)
    

    var m map[string]*BearerConfigItem

    //this.Configs = make(map[string]*BearerConfigItem)
    err = json.Unmarshal(data, &m)
    if err != nil 
        log.Panic(err)
    

    this.Configs = m


func IndexOf(array []string, item string) int 
    for i := 0; i < len(array); i++ 
        if array[i] == item 
            return i
        
    

    return -1

当然了,其实这个只适合内部简单使用,并不适合对外的真实的OpenAPI,因为Token现在太死了,Token应该是另外一个系统(鉴权中心)里边的处理的。包括企业自建应用的信息创建、Token的兑换、刷新等等。并且,不光是业务逻辑,还有非常强烈的性能要求,毕竟OpenAPI可以说是一个企业公开接口的门户了,跟这种软件打交道,性能也不能差了(我们公司这边我们团队也做了这么一个系统,鉴权接口可以单机1W QPS,响应时间4ms),当然也是要花费不少心思的。

 

最后,这个项目已经开源了,给大家做个简单的参考。

https://gitee.com/anxin1225/OpenAPI.GO

技术图片

 

go语言实现一个简单的简单网关

...网关;还有就是golang的网关,比如tyk。这篇文章主要是讲如何基于golang实现一个简单的网关。转自:troy.wang/docs/golang/posts/golang-gateway/整理:go语言钟文文档:www.topgoer.cn启动两个后端web服务(代码)这里使用命令行工具进行测试具... 查看详情

如何使用 kong api 网关实现 oauth2?

】如何使用kongapi网关实现oauth2?【英文标题】:HowdoIimplementoauth2withkongapigateway?【发布时间】:2020-04-1604:22:50【问题描述】:我想为在laravel上运行的一堆微服务实现一个api网关。在网关前面有一个角度客户端,用户必须使用用户... 查看详情

如何使用api网关做服务编排?

参考技术A服务编排/数据聚合指的是可以通过一个请求来依次调用多个微服务,并对每个服务的返回结果做数据处理,最终整合成一个大的结果返回给前端。例如一个服务是“查询用户预定的酒店”,前端仅需要传一个订单ID,... 查看详情

elasticsearch:api网关apacheapisix集成elasticsearch实现实时日志监控(代码片段)

...绍ApacheAPISIX的elasticsearch-logger插件的相关信息,以及如何通过此插件获取APISIX的实时日志。背景信息​ApacheAPISIX 是一个动态、实时、高性能的 API 网关,提供了负载均衡、动态上游、灰度发布、服务熔断、身份认证、可... 查看详情

原创自己动手写一个服务网关(代码片段)

引言什么是网关?为什么需要使用网关?如图所示,在不使用网关的情况下,我们的服务是直接暴露给服务调用方。当调用方增多,势必需要添加定制化访问权限、校验等逻辑。当添加API网关后,再第三方调用端和服务提供方之... 查看详情

如何判断golang接口是否实现?(代码片段)

前言在看一个底层库的的时候,看到了一个比较奇怪的写法,于是乎有了本文。主要探讨两个问题:1.利用编译来判断Golang接口是否实现2.延伸出的make和new的区别正文1.利用编译来判断Golang接口是否实现看了一个底层... 查看详情

golang使用martini和redigo在go中编写的一个小例子api(代码片段)

查看详情

golang接口的使用(练习一)(代码片段)

在go语言中,一个类只要实现了接口要求的所有函数,我们就说这个类实现了这个接口。golang接口赋值实现方式一:将对象实例赋值给接口packagemainimport"fmt"//定义一个Animal接口,实现飞和跑的功能typeAnimalinterfaceFly()Run()//定义一个Bi... 查看详情

ocelot.jwtauthorize:一个基于网关的jwt验证包(代码片段)

...cnblogs.com/axzxs2001/p/8005084.html,作过说明,这篇博文说明了实现代码,今天我把这个实现作了整理,封装成一个Nuget包,供大家方便调用。WebAPI的验证一 查看详情

golang使用一个二叉树来实现一个插入排序(代码片段)

思路不太好理解,请用断点packagemainimport"fmt"typetreestructvalueintleft,right*treefuncSort(values[]int)varroot*treefor_,v:=rangevaluesroot=add(root,v)appendValues(values[:0],root)funcappendValues(values[]i 查看详情

x的平方根的golang实现(代码片段)

...整数,小数部分将被舍去。首先遇到这种题目肯定要想到使用内置得api来解答://使用api来求解funcmySqrt(xint)intf:=float64(x)ff:=math. 查看详情

golang教程:goroutine信道(代码片段)

在上一篇教程中,我们讨论了如何使用协程实现并发。在这篇教程中,我们将讨论信道以及如何使用信道实现协程间通信。什么是信道信道(Channel)可以被认为是协程之间通信的管道。与水流从管道的一端流向另一端一样,数... 查看详情

使用golang实现目录的监控过程(代码片段)

GO实现文件夹监控收获查看watcher.go,看出实现一个系统event的监控,代码不过625行;执行exec.Cmd(),cmd.Run(),可以获得shell的执行状态;开始阅读github上的开源代码,代码特别精简;说明项目组有一个需求,即当团队人员更新Gitbook到... 查看详情

golang中如何释放websocket和redis网关服务器资源?

】golang中如何释放websocket和redis网关服务器资源?【英文标题】:Howtoreleaseawebsocketandredisgatewayserverresourceingolang?【发布时间】:2019-04-1207:06:36【问题描述】:我有一个网关服务器,它可以使用websocket将消息推送到客户端,一个新... 查看详情

3分钟就会系列使用ocelot+consul搭建微服务吧!(代码片段)

...来的客户。手把手搭建一个网关在此之前你应该去学一学如何搭建服务集群,那么这将有效与你学习API网关服务,传送门,再此基础上再添加一个名为  MicroService.APIGetway的ASP.NETWebApi项目。在该项目中,我们通过Nuget安装Oc... 查看详情

如何使用gateway搭建网关服务及实现动态路由?(代码片段)

...的一部分,是必须要掌握的;本文记录一下我是如何使用Gateway搭建网关服务及实现动态路由的,帮助大家学习如何快速搭建一个网关服务,了解路由相关配置,鉴权的流程及业务处理,有兴趣的一定看到... 查看详情

如何加速golang写业务的开发速度(代码片段)

如何加速golang写业务的开发速度不要忌讳panicgolang写业务代码经常会被吐槽,写业务太慢了,其中最大的吐槽点就是,处理各种error太麻烦了。一个项目中,会有30%或者更多的是在处理error。对于golang的error这个事情,golang的官方... 查看详情

用golang实现一个代理池(代码片段)

...总会遇到爬取速度过快而被封IP的情况,这个时候就需要使用代理了。在https://github.com/henson/ProxyPool的启发下,决定自己实现一个代理池。项目已经开源在github。https://github.com/AceDarkkinght/GoProxyCollector开发环境windows7,Go1.8.4数据来... 查看详情