Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support nginx conf #1130

Merged
merged 23 commits into from
Nov 10, 2023
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
finish nginx convert code
  • Loading branch information
suchen-sci committed Nov 3, 2023
commit 977554fd89ba7f015ea70d9fba8f381cc4e3b075
17 changes: 17 additions & 0 deletions cmd/client/commandv2/common/spec.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ package common
import (
"github.com/megaease/easegress/v2/cmd/client/general"
"github.com/megaease/easegress/v2/pkg/filters"
"github.com/megaease/easegress/v2/pkg/filters/builder"
"github.com/megaease/easegress/v2/pkg/filters/proxies/httpproxy"
"github.com/megaease/easegress/v2/pkg/object/httpserver"
"github.com/megaease/easegress/v2/pkg/object/pipeline"
Expand Down Expand Up @@ -82,6 +83,22 @@ func NewProxyFilterSpec(name string) *httpproxy.Spec {
return spec
}

// NewWebsocketFilterSpec returns a new WebsocketFilterSpec.
func NewWebsocketFilterSpec(name string) *httpproxy.WebSocketProxySpec {
spec := GetDefaultFilterSpec(httpproxy.WebSocketProxyKind).(*httpproxy.WebSocketProxySpec)
spec.BaseSpec.MetaSpec.Name = name
spec.BaseSpec.MetaSpec.Kind = httpproxy.WebSocketProxyKind
return spec
}

// NewRequestAdaptorFilterSpec returns a new RequestAdaptorFilterSpec.
func NewRequestAdaptorFilterSpec(name string) *builder.RequestAdaptorSpec {
spec := GetDefaultFilterSpec(builder.RequestAdaptorKind).(*builder.RequestAdaptorSpec)
spec.BaseSpec.MetaSpec.Name = name
spec.BaseSpec.MetaSpec.Kind = builder.RequestAdaptorKind
return spec
}

// GetDefaultFilterSpec returns the default filter spec of the kind.
func GetDefaultFilterSpec(kind string) filters.Spec {
return filters.GetKind(kind).DefaultSpec()
Expand Down
14 changes: 7 additions & 7 deletions cmd/client/commandv2/convert/nginx/cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@
package nginx

import (
"encoding/json"
"fmt"
"math/rand"

Expand Down Expand Up @@ -57,12 +56,13 @@ func Cmd() *cobra.Command {
if err != nil {
general.ExitWithError(err)
}
fmt.Println(hs, pls)
data, err := json.Marshal(payload)
if err != nil {
general.ExitWithError(err)
}
fmt.Println(string(data))
printYaml(hs)
printYaml(pls)
// data, err := json.Marshal(payload)
// if err != nil {
// general.ExitWithError(err)
// }
// fmt.Println(string(data))
},
}
cmd.Flags().StringVarP(&flags.NginxConf, "file", "f", "", "nginx.conf file path")
Expand Down
143 changes: 138 additions & 5 deletions cmd/client/commandv2/convert/nginx/convert.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,19 @@ package nginx

import (
"fmt"
"regexp"
"sort"
"strings"

"github.com/megaease/easegress/v2/cmd/client/commandv2/common"
"github.com/megaease/easegress/v2/cmd/client/general"
"github.com/megaease/easegress/v2/pkg/filters"
"github.com/megaease/easegress/v2/pkg/filters/builder"
"github.com/megaease/easegress/v2/pkg/filters/proxies"
"github.com/megaease/easegress/v2/pkg/filters/proxies/httpproxy"
"github.com/megaease/easegress/v2/pkg/object/httpserver/routers"
"github.com/megaease/easegress/v2/pkg/protocols/httpprot/httpheader"
"github.com/megaease/easegress/v2/pkg/util/codectool"
)

func convertConfig(options *Options, config *Config) ([]*common.HTTPServerSpec, []*common.PipelineSpec, error) {
Expand Down Expand Up @@ -68,6 +76,9 @@ func convertServerBase(spec *common.HTTPServerSpec, base ServerBase) *common.HTT
return spec
}

// convertRule converts nginx conf to easegress rule.
// exact path > prefix path > regexp path.
// prefix path should be sorted by path length.
func convertRule(options *Options, rule *Rule) (*routers.Rule, []*common.PipelineSpec) {
router := &routers.Rule{
Hosts: make([]routers.Host, 0),
Expand All @@ -79,35 +90,157 @@ func convertRule(options *Options, rule *Rule) (*routers.Rule, []*common.Pipelin
IsRegexp: h.IsRegexp,
})
}

pipelines := make([]*common.PipelineSpec, 0)
exactPaths := make([]*routers.Path, 0)
prefixPaths := make([]*routers.Path, 0)
rePaths := make([]*routers.Path, 0)
for _, p := range rule.Paths {
name := options.GetPipelineName(p.Path)
path := routers.Path{
path := &routers.Path{
Backend: name,
}
// path for websocket should not limit body size.
if isWebsocket(p.Backend) {
path.ClientMaxBodySize = -1
}
switch p.Type {
case PathTypeExact:
path.Path = p.Path
exactPaths = append(exactPaths, path)
case PathTypePrefix:
path.PathPrefix = p.Path
prefixPaths = append(prefixPaths, path)
case PathTypeRe:
path.PathRegexp = p.Path
rePaths = append(rePaths, path)
case PathTypeReInsensitive:
path.PathRegexp = fmt.Sprintf("(?i)%s", p.Path)
rePaths = append(rePaths, path)
default:
general.Warnf("unknown path type: %s", p.Type)
}
router.Paths = append(router.Paths, &path)
pipelineSpec := convertProxy(name, p.Backend)
pipelines = append(pipelines, pipelineSpec)
}
sort.Slice(prefixPaths, func(i, j int) bool {
return prefixPaths[i].Path > prefixPaths[j].Path
})
router.Paths = append(router.Paths, exactPaths...)
router.Paths = append(router.Paths, prefixPaths...)
router.Paths = append(router.Paths, rePaths...)
return router, pipelines
}

func convertProxy(name string, info *ProxyInfo) *common.PipelineSpec {
pipeline := common.NewPipelineSpec(name)
// TODO: set header, update proxy filter
proxy := common.NewProxyFilterSpec("proxy")
pipeline.SetFilters([]filters.Spec{proxy})

flow := make([]filters.Spec, 0)
if len(info.SetHeaders) != 0 {
adaptor := getRequestAdaptor(info)
flow = append(flow, adaptor)
}

var proxy filters.Spec
if isWebsocket(info) {
proxy = getWebsocketFilter(info)
} else {
proxy = getProxyFilter(info)
}
flow = append(flow, proxy)
pipeline.SetFilters(flow)
return pipeline
}

var nginxEmbeddedVarRe *regexp.Regexp
var nginxToTemplateMap = map[string]string{
"$host": ".req.Host",
"$hostname": ".req.Host",
"$content_length": `header .req.Header "Content-Length"`,
"$content_type": `header .req.Header "Content-Type"`,
"$remote_addr": ".req.RemoteAddr",
"$remote_user": "username .req",
"$request_body": ".req.Body",
"$request_method": ".req.Method",
"$request_uri": ".req.RequestURI",
"$scheme": ".req.URL.Scheme",
}

func translateNginxEmbeddedVar(s string) string {
if nginxEmbeddedVarRe == nil {
nginxEmbeddedVarRe = regexp.MustCompile(`\$[a-zA-Z0-9_]+`)
}
return nginxEmbeddedVarRe.ReplaceAllStringFunc(s, func(s string) string {
newValue := nginxToTemplateMap[s]
if newValue == "" {
newValue = s
msg := "nginx embedded value %s is not supported now, "
msg += "please check easegress RequestAdaptor filter for more information about template."
general.Warnf(msg, s)
}
return fmt.Sprintf("{{ %s }}", newValue)
})
}

func getRequestAdaptor(info *ProxyInfo) *builder.RequestAdaptorSpec {
spec := common.NewRequestAdaptorFilterSpec("request-adaptor")
template := &builder.RequestAdaptorTemplate{
Header: &httpheader.AdaptSpec{
Set: make(map[string]string),
},
}
for k, v := range info.SetHeaders {
if k == "Upgrade" || k == "Connection" {
continue
}
template.Header.Set[k] = translateNginxEmbeddedVar(v)
}
data := codectool.MustMarshalYAML(template)
spec.Template = string(data)
return spec
}

func isWebsocket(info *ProxyInfo) bool {
return info.SetHeaders["Upgrade"] != "" && info.SetHeaders["Connection"] != ""
}

func getWebsocketFilter(info *ProxyInfo) *httpproxy.WebSocketProxySpec {
for i, s := range info.Servers {
s.Server = strings.Replace(s.Server, "http:https://", "ws:https://", 1)
s.Server = strings.Replace(s.Server, "https://", "wss:https://", 1)
info.Servers[i] = s
}
spec := common.NewWebsocketFilterSpec("websocket")
spec.Pools = []*httpproxy.WebSocketServerPoolSpec{{
BaseServerPoolSpec: getBaseServerPool(info),
}}
return spec
}

func getProxyFilter(info *ProxyInfo) *httpproxy.Spec {
spec := common.NewProxyFilterSpec("proxy")
spec.Pools = []*httpproxy.ServerPoolSpec{{
BaseServerPoolSpec: getBaseServerPool(info),
}}
return spec
}

func getBaseServerPool(info *ProxyInfo) httpproxy.BaseServerPoolSpec {
servers := make([]*proxies.Server, len(info.Servers))
policy := proxies.LoadBalancePolicyRoundRobin
for i, s := range info.Servers {
servers[i] = &proxies.Server{
URL: s.Server,
Weight: s.Weight,
}
if s.Weight != 1 {
policy = proxies.LoadBalancePolicyWeightedRandom
}
}
return httpproxy.BaseServerPoolSpec{
Servers: servers,
LoadBalance: &proxies.LoadBalanceSpec{
Policy: policy,
},
}
}
58 changes: 58 additions & 0 deletions cmd/client/commandv2/convert/nginx/convert_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,61 @@
*/

package nginx

import (
"fmt"
"net/http"
"strings"
"testing"

"github.com/megaease/easegress/v2/pkg/filters"
"github.com/megaease/easegress/v2/pkg/filters/builder"
"github.com/megaease/easegress/v2/pkg/protocols/httpprot"
"github.com/stretchr/testify/assert"
)

func TestGetRequestAdaptor(t *testing.T) {
assert := assert.New(t)
req, err := http.NewRequest("GET", "http:https://example.com", strings.NewReader("test"))
req.Header.Set("Content-Length", "4")
req.Header.Set("Content-Type", "text/plain")
req.SetBasicAuth("user", "pass")
req.RemoteAddr = "localhost:8080"
req.RequestURI = "/apis/v1"
assert.Nil(err)
ctx := newContext(t, req)
info := &ProxyInfo{
SetHeaders: map[string]string{
"X-Host": "$host",
"X-Hostname": "$hostname",
"X-Content": "$content_length",
"X-Content-Type": "$content_type",
"X-Remote-Addr": "$remote_addr",
"X-Remote-User": "$remote_user",
"X-Request-Body": "$request_body",
"X-Method": "$request_method",
"X-Request-URI": "$request_uri",
"X-Scheme": "$scheme",
},
}
spec := getRequestAdaptor(info)
ra := filters.GetKind(builder.RequestAdaptorKind).CreateInstance(spec)
ra.Init()
ra.Handle(ctx)
h := ctx.GetInputRequest().(*httpprot.Request).Header()
expected := map[string]string{
"X-Host": "example.com",
"X-Hostname": "example.com",
"X-Content": "4",
"X-Content-Type": "text/plain",
"X-Remote-Addr": "localhost:8080",
"X-Remote-User": "user",
"X-Request-Body": "test",
"X-Method": "GET",
"X-Request-URI": "/apis/v1",
"X-Scheme": "http",
}
for k, v := range expected {
assert.Equal(v, h.Get(k), fmt.Sprintf("header %s", k))
}
}
9 changes: 8 additions & 1 deletion cmd/client/commandv2/convert/nginx/parse_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,13 @@ func TestParsePayload(t *testing.T) {
location /upstream {
proxy_pass http:https://backend;
}

location /websocket {
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
proxy_pass http:https://localhost:9090;
}
}
}
`
Expand All @@ -190,7 +197,7 @@ func TestParsePayload(t *testing.T) {
opt.init()
hs, pls, err := convertConfig(opt, config)
assert.Nil(t, err)
printYaml(hs)
printJson(hs)
printYaml(pls)
}
}
18 changes: 18 additions & 0 deletions cmd/client/commandv2/convert/nginx/test_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,22 @@
package nginx

import (
"net/http"
"os"
"path/filepath"
"testing"

"github.com/megaease/easegress/v2/pkg/context"
"github.com/megaease/easegress/v2/pkg/logger"
"github.com/megaease/easegress/v2/pkg/protocols/httpprot"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

func init() {
logger.InitMock()
}

type tempTestDir struct {
dir string
t *testing.T
Expand Down Expand Up @@ -54,3 +63,12 @@ func (dir *tempTestDir) Clean() {
}
os.Remove(dir.dir)
}

func newContext(t *testing.T, req *http.Request) *context.Context {
ctx := context.New(nil)
r, err := httpprot.NewRequest(req)
assert.Nil(t, err)
r.FetchPayload(0)
ctx.SetRequest(context.DefaultNamespace, r)
return ctx
}
3 changes: 2 additions & 1 deletion cmd/client/commandv2/convert/nginx/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,13 @@ package nginx
import (
"encoding/json"
"fmt"
"strings"

"gopkg.in/yaml.v3"
)

func directiveInfo(d *Directive) string {
return fmt.Sprintf("directive %s in line %d of file %s", d.Directive, d.Line, d.File)
return fmt.Sprintf("directive <%s %s> of %s:%d", d.Directive, strings.Join(d.Args, " "), d.File, d.Line)
}

func printJson(v interface{}) {
Expand Down
Loading
Loading