Skip to content

Commit

Permalink
support web server
Browse files Browse the repository at this point in the history
  • Loading branch information
likezhang committed Jul 9, 2022
1 parent b5ec047 commit 075db1e
Show file tree
Hide file tree
Showing 7 changed files with 416 additions and 23 deletions.
83 changes: 60 additions & 23 deletions grpcmux/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,14 @@ import (
"context"
"flag"
"fmt"
"github.com/skema-dev/skema-go/config"
"github.com/skema-dev/skema-go/logging"
"io/ioutil"
"log"
"net"
"net/http"
"os"
"strings"
"text/template"

"github.com/skema-dev/skema-go/config"
"github.com/skema-dev/skema-go/logging"

"github.com/grpc-ecosystem/grpc-gateway/v2/runtime"
"google.golang.org/grpc"
Expand Down Expand Up @@ -60,6 +58,10 @@ func NewServerWithConfig(conf *config.Config, opts ...grpc.ServerOption) *grpcSe
}
logging.Infow("service port", "gprc", port, "http", httpPort)

if !validateHttpConfig(conf) {
logging.Fatalf("duplicated url path found. please fix the grpc config file")
}

// connect to grpc port
conn, err := grpc.DialContext(
context.Background(),
Expand All @@ -86,7 +88,6 @@ func NewServerWithConfig(conf *config.Config, opts ...grpc.ServerOption) *grpcSe
gatewayPathPrefix += "/"
}
}
logging.Infof("gateway path is set to %s", gatewayPathPrefix)
}

initComponents(conf)
Expand Down Expand Up @@ -136,6 +137,28 @@ func LoadLocalConfig() *config.Config {
return config.NewConfigWithFile(path)
}

func validateHttpConfig(conf *config.Config) bool {
gatewayPath := conf.GetString("http.gateway.path", "")
staticPath := conf.GetString("http.static.path", "")
swaggerPath := conf.GetString("http.swagger.path", "")

values := []string{gatewayPath, staticPath, swaggerPath}
for i := range values {
if values[i] == "" {
continue
}
j := i + 1
for j < len(values) {
if values[i] == values[j] {
logging.Errorf("duplicated url prefix path: %s, %s\n", values[i], values[j])
return false
}
j += 1
}
}
return true
}

// Requeired for grpc service registration
func (g *grpcServer) RegisterService(desc *grpc.ServiceDesc, impl interface{}) {
g.server.RegisterService(desc, impl)
Expand All @@ -152,7 +175,7 @@ func (g *grpcServer) ServeHTTP(w http.ResponseWriter, r *http.Request) {
r.URL.Path = strings.TrimPrefix(r.URL.Path, path)
r.RequestURI = strings.TrimPrefix(r.RequestURI, path)
}

logging.Infof("server http %s\n", r.URL.Path)
g.gatewayMux.ServeHTTP(w, r)
}

Expand All @@ -161,6 +184,32 @@ func (g *grpcServer) Serve() error {
reflection.Register(g.server)

g.httpMux.Handle(g.gatewayRoutePath, g)
logging.Infof("grpc-gateway path: %s", g.gatewayRoutePath)

if g.conf.GetString("http.static.path", "") != "" {
staticPath := g.conf.GetString("http.static.path")
if !strings.HasSuffix(staticPath, "/") {
staticPath += "/"
}
staticFilepath := g.conf.GetString("http.static.filepath")
staticHandler := http.FileServer(http.Dir(staticFilepath))

g.httpMux.Handle(staticPath, http.StripPrefix(staticPath, staticHandler))

logging.Infof("static content(%s) path: %s", staticFilepath, staticPath)
}

if g.conf.GetString("http.swagger.path", "") != "" {
swaggerPath := g.conf.GetString("http.swagger.path")
swaggerPath = strings.TrimSuffix(swaggerPath, "/")
swaggerFilepath := g.conf.GetString("http.swagger.filepath")
swaggerHandler, openapiHandler := g.getSwaggerHandler(swaggerFilepath)

g.httpMux.Handle(swaggerPath, swaggerHandler)
g.httpMux.Handle(fmt.Sprintf("%s/openapi", swaggerPath), openapiHandler)

logging.Infof("swagger path: %s", swaggerPath)
}

lis, err := net.Listen("tcp", fmt.Sprintf(":%d", g.port))
if err != nil {
Expand All @@ -184,30 +233,18 @@ func (g *grpcServer) GetGatewayInfo() (context.Context, *runtime.ServeMux, grpc.
return g.ctx, g.gatewayMux, g.clientConn
}

func (g *grpcServer) EnableSwagger(serviceName string, openapiDescFilepath string) error {
swaggerUrl := fmt.Sprintf("/%s/swagger/openapi", serviceName)
swaggerServingUrl := fmt.Sprintf("/%s/swagger", serviceName)

getSwagger := func(w http.ResponseWriter, r *http.Request, pathParams map[string]string) {
func (g *grpcServer) getSwaggerHandler(openapiDescFilepath string) (http.HandlerFunc, http.HandlerFunc) {
openapiHandler := func(w http.ResponseWriter, r *http.Request) {
if content, err := ioutil.ReadFile(openapiDescFilepath); err == nil {
fmt.Fprint(w, string(content))
} else {
log.Fatal(err)
}
}

swaggerServing := func(w http.ResponseWriter, r *http.Request, pathParams map[string]string) {
t1, err := template.New("index").Parse(swaggerTpl)
if err != nil {
panic(err)
}
t1.Execute(w, swaggerUrl)
swaggerHandler := func(w http.ResponseWriter, r *http.Request) {
fmt.Fprint(w, swaggerTpl)
}

g.gatewayMux.HandlePath("GET", swaggerUrl, getSwagger)
g.gatewayMux.HandlePath("GET", swaggerServingUrl, swaggerServing)

logging.Infof("swagger enabled at url: %s\n", swaggerServingUrl)

return nil
return swaggerHandler, openapiHandler
}
25 changes: 25 additions & 0 deletions sample/webserver/grpc.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
port: 9991 # for grpc service
http:
port: 9992 # for http service
gateway:
path: "/"
static:
path: "/web"
filepath: "./static"
# Another setting (you should modify the js code for backend api url)
# gateway:
# path: "/backend"
# static:
# path: "/"
# filepath: "./static"
swagger:
path: "/swagger"
filepath: "./swagger.json"
client:
address: "localhost:9993" # for grpc client url and port


logging:
level: debug # info | debug
encoding: console # console | json
output: "./log/default.log"
32 changes: 32 additions & 0 deletions sample/webserver/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package main

import (
_ "embed"

"github.com/skema-dev/skema-go/config"
"github.com/skema-dev/skema-go/grpcmux"
"github.com/skema-dev/skema-go/logging"
pb "github.com/skema-dev/skema-go/sample/api/skema/test"
)

//go:embed grpc.yaml
var yamlConfig []byte

func main() {
grpcSrv := grpcmux.NewServerWithConfig(
config.NewConfigWithString(string(yamlConfig)),
)

pb.RegisterTestServer(grpcSrv, NewServer())

// for http gateway only.
ctx, mux, conn := grpcSrv.GetGatewayInfo()
pb.RegisterTestHandlerClient(ctx, mux, pb.NewTestClient(conn))

//grpcSrv.EnableStaticContent("/", "./static")
//grpcSrv.EnableStaticContent("/script", "./static/script")

if err := grpcSrv.Serve(); err != nil {
logging.Fatalf("Serve error %v", err.Error())
}
}
49 changes: 49 additions & 0 deletions sample/webserver/server.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package main

import (
"context"
"log"

pb "github.com/skema-dev/skema-go/sample/api/skema/test"
)

type rpcTestServer struct {
pb.UnimplementedTestServer
}

// NewServer: Create new grpc server instance
func NewServer() pb.TestServer {
svr := &rpcTestServer{
// init custom fileds
}
return svr
}

// Heathcheck
func (s *rpcTestServer) Heathcheck(
ctx context.Context,
req *pb.HealthcheckRequest,
) (rsp *pb.HealthcheckResponse, err error) {
// implement business logic here ...
// ...

log.Printf("Received from Heathcheck request: %v", req)
rsp = &pb.HealthcheckResponse{
Result: "health check ok",
}

return rsp, err
}

// Helloworld
func (s *rpcTestServer) Helloworld(ctx context.Context, req *pb.HelloRequest) (rsp *pb.HelloReply, err error) {
// implement business logic here ...
// ...

log.Printf("Received from Helloworld request: %v", req)
rsp = &pb.HelloReply{
Msg: "Hello world",
Code: "0",
}
return rsp, err
}
17 changes: 17 additions & 0 deletions sample/webserver/static/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<html>
<head>
<meta http-equiv="Cache-control" content="no-cache">
<meta http-equiv="Expires" content="-1">
<link rel="stylesheet" href="https://cdn.simplecss.org/simple.min.css">
<meta name="viewport" content="width=device-width, initial-scale=1">

<script src="https://unpkg.com/react@18/umd/react.production.min.js" crossorigin></script>
<script src="https://unpkg.com/react-dom@18/umd/react-dom.production.min.js" crossorigin></script>
<script src="https://unpkg.com/babel-standalone@6/babel.min.js"></script>
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
</head>
<body>
<div id="main_container"></div>
<script src="/web/script/main.js" type="text/babel"></script>
</body>
</html>
92 changes: 92 additions & 0 deletions sample/webserver/static/script/main.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
'use strict';

class Lesson extends React.Component {
constructor(props) {
console.log(props)
super(props);
this.state = {
lessonId: props.lessonId,
name: props.name,
description: props.description
}
}

loadLession() {
let lessons = new Map()
lessons.set("lesson1", <Lesson1 />)
lessons.set("lesson2", <Lesson2 />)
lessons.set("lesson3", <Lesson3 />)
root.render(lessons.get(this.state.name));
}

render() {
return (
<div>
<button onClick={() => this.loadLession() }>
{ this.state.name }
</button>
<br/>
{ this.state.description }
</div>
);
}
}

class ApiList extends React.Component {
constructor(props) {
console.log(props)
super(props);
this.state = {
result: ""
}
this.healthCheck = this.healthCheck.bind(this);
this.helloWorld = this.helloWorld.bind(this);
}

healthCheck() {
axios
.get('/api/healthcheck')
.then(response => {
this.setState({result: "healthcheck: " + response.data.result});
})
.catch(function (error) { // 请求失败处理
console.log(error);
});
}

helloWorld() {
axios
.post('/api/helloworld')
.then(response => {
this.setState({result: "helloworld:" + response.data.msg});
})
.catch(function (error) { // 请求失败处理
console.log(error);
});
}

render() {
return (
<div style={{ textAlign: "center" }}>
<b>API List</b>
<hr/>
<button onClick={this.healthCheck}>
Call HealthCheck
</button>
<br/>
<br/>
<button onClick={this.helloWorld}>
Call HelloWorld
</button>
<br/>
<br/>
{ this.state.result }
</div>
);
}
}


const domContainer1 = document.querySelector('#main_container');
const root = ReactDOM.createRoot(domContainer1);
root.render(<ApiList />);
Loading

0 comments on commit 075db1e

Please sign in to comment.