> For the complete documentation index, see [llms.txt](https://eddycjy.gitbook.io/golang/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://eddycjy.gitbook.io/golang/di-4-ke-grpc/grpc-http.md).

# 4.7 让你的服务同时提供 HTTP 接口

项目地址：<https://github.com/EDDYCJY/go-grpc-example>

## 前言

* 接口需要提供给其他业务组访问，但是 RPC 协议不同无法内调，对方问能否走 HTTP 接口，怎么办？
* 微信（公众号、小程序）等第三方回调接口只支持 HTTP 接口，怎么办

我相信你在实际工作中都会遇到如上问题，在 gRPC 中都是有解决方案的，本章节将会进行介绍 🤔

## 为什么可以同时提供 HTTP 接口

关键一点，gRPC 的协议是基于 HTTP/2 的，因此应用程序能够在单个 TCP 端口上提供 HTTP/1.1 和 gRPC 接口服务（两种不同的流量）

## 怎么同时提供 HTTP 接口

### 检测协议

```
if r.ProtoMajor == 2 && strings.Contains(r.Header.Get("Content-Type"), "application/grpc") {
    server.ServeHTTP(w, r)
} else {
    mux.ServeHTTP(w, r)
}
```

### 流程

1. 检测请求协议是否为 HTTP/2
2. 判断 Content-Type 是否为 application/grpc（gRPC 的默认标识位）
3. 根据协议的不同转发到不同的服务处理

## gRPC

### TLS

在前面的章节，为了便于展示因此没有简单封装

在本节需复用代码，重新封装了，可详见：[go-grpc-example](https://github.com/EDDYCJY/go-grpc-example/tree/master/pkg/gtls)

### 目录结构

新建 simple\_http\_client、simple\_http\_server 目录，目录结构如下：

```
go-grpc-example
├── client
│   ├── simple_client
│   ├── simple_http_client
│   └── stream_client
├── conf
├── pkg
│   └── gtls
├── proto
├── server
│   ├── simple_http_server
│   ├── simple_server
│   └── stream_server
```

### Server

在 simple\_http\_server 目录下新建 server.go，写入文件内容：

```
package main

import (
    "context"
    "log"
    "net/http"
    "strings"

    "github.com/EDDYCJY/go-grpc-example/pkg/gtls"
    pb "github.com/EDDYCJY/go-grpc-example/proto"

    "google.golang.org/grpc"
)

type SearchService struct{}

func (s *SearchService) Search(ctx context.Context, r *pb.SearchRequest) (*pb.SearchResponse, error) {
    return &pb.SearchResponse{Response: r.GetRequest() + " HTTP Server"}, nil
}

const PORT = "9003"

func main() {
    certFile := "../../conf/server/server.pem"
    keyFile := "../../conf/server/server.key"
    tlsServer := gtls.Server{
        CertFile: certFile,
        KeyFile:  keyFile,
    }

    c, err := tlsServer.GetTLSCredentials()
    if err != nil {
        log.Fatalf("tlsServer.GetTLSCredentials err: %v", err)
    }

    mux := GetHTTPServeMux()

    server := grpc.NewServer(grpc.Creds(c))
    pb.RegisterSearchServiceServer(server, &SearchService{})

    http.ListenAndServeTLS(":"+PORT,
        certFile,
        keyFile,
        http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
            if r.ProtoMajor == 2 && strings.Contains(r.Header.Get("Content-Type"), "application/grpc") {
                server.ServeHTTP(w, r)
            } else {
                mux.ServeHTTP(w, r)
            }

            return
        }),
    )
}

func GetHTTPServeMux() *http.ServeMux {
    mux := http.NewServeMux()
    mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
        w.Write([]byte("eddycjy: go-grpc-example"))
    })

    return mux
}
```

* http.NewServeMux：创建一个新的 ServeMux，ServeMux 本质上是一个路由表。它默认实现了 ServeHTTP，因此返回 Handler 后可直接通过 HandleFunc 注册 pattern 和处理逻辑的方法
* http.ListenAndServeTLS：可简单的理解为提供监听 HTTPS 服务的方法，重点的协议判断转发，也在这里面

其实，你理解后就会觉得很简单，核心步骤：判断 -> 转发 -> 响应。我们改变了前两步的默认逻辑，仅此而已

### Client

在 simple\_http\_server 目录下新建 client.go，写入文件内容：

```
package main

import (
    "context"
    "log"

    "google.golang.org/grpc"

    "github.com/EDDYCJY/go-grpc-example/pkg/gtls"
    pb "github.com/EDDYCJY/go-grpc-example/proto"
)

const PORT = "9003"

func main() {
    tlsClient := gtls.Client{
        ServerName: "go-grpc-example",
        CertFile:   "../../conf/server/server.pem",
    }
    c, err := tlsClient.GetTLSCredentials()
    if err != nil {
        log.Fatalf("tlsClient.GetTLSCredentials err: %v", err)
    }

    conn, err := grpc.Dial(":"+PORT, grpc.WithTransportCredentials(c))
    if err != nil {
        log.Fatalf("grpc.Dial err: %v", err)
    }
    defer conn.Close()

    client := pb.NewSearchServiceClient(conn)
    resp, err := client.Search(context.Background(), &pb.SearchRequest{
        Request: "gRPC",
    })
    if err != nil {
        log.Fatalf("client.Search err: %v", err)
    }

    log.Printf("resp: %s", resp.GetResponse())
}
```

## 验证

### gRPC Client

```
$ go run client.go 
2018/10/04 14:56:56 resp: gRPC HTTP Server
```

### HTTP/1.1 访问

![image](https://image.eddycjy.com/1d92cb9e949e32eef7f8a64a6a77deb9.jpg)

## 总结

通过本章节，表面上完成了同端口提供双服务的功能，但实际上，应该是加深了 HTTP/2 的理解和使用，这才是本质

## 拓展

如果你有一个需求，是要**同时提供** RPC 和 RESTful JSON API 两种接口的，不要犹豫，点进去：[gRPC + gRPC Gateway 实践](https://segmentfault.com/a/1190000013339403)

## 问题

你以为这个方案就万能了吗，不。Envoy Proxy 的支持就不完美，无法同时监听一个端口的两种流量 😤

## 参考

### 本系列示例代码

* [go-grpc-example](https://github.com/EDDYCJY/go-grpc-example)


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://eddycjy.gitbook.io/golang/di-4-ke-grpc/grpc-http.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
