feat: Nginx 配置支持 Lua 模块 (#3844)

This commit is contained in:
zhengkunwang 2024-02-06 14:02:12 +08:00 committed by GitHub
parent 4fc26a3061
commit fb06e29aa3
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
15 changed files with 353 additions and 76 deletions

View file

@ -37,7 +37,10 @@ func getNginxFull(website *model.Website) (dto.NginxFull, error) {
if err != nil {
return nginxFull, err
}
config := parser.NewStringParser(string(content)).Parse()
config, err := parser.NewStringParser(string(content)).Parse()
if err != nil {
return dto.NginxFull{}, err
}
config.FilePath = nginxConfig.FilePath
nginxConfig.OldContent = string(content)
nginxConfig.Config = config
@ -54,7 +57,10 @@ func getNginxFull(website *model.Website) (dto.NginxFull, error) {
if err != nil {
return nginxFull, err
}
siteConfig := parser.NewStringParser(string(siteNginxContent)).Parse()
siteConfig, err := parser.NewStringParser(string(siteNginxContent)).Parse()
if err != nil {
return dto.NginxFull{}, err
}
siteConfig.FilePath = siteConfigPath
siteNginxConfig.Config = siteConfig
siteNginxConfig.OldContent = string(siteNginxContent)
@ -156,15 +162,22 @@ func deleteNginxConfig(scope string, params []dto.NginxParam, website *model.Web
}
func getNginxParamsFromStaticFile(scope dto.NginxKey, newParams []dto.NginxParam) []dto.NginxParam {
newConfig := &components.Config{}
var (
newConfig = &components.Config{}
err error
)
updateScope := "in"
switch scope {
case dto.SSL:
newConfig = parser.NewStringParser(string(nginx_conf.SSL)).Parse()
newConfig, err = parser.NewStringParser(string(nginx_conf.SSL)).Parse()
case dto.CACHE:
newConfig = parser.NewStringParser(string(nginx_conf.Cache)).Parse()
newConfig, err = parser.NewStringParser(string(nginx_conf.Cache)).Parse()
case dto.ProxyCache:
newConfig = parser.NewStringParser(string(nginx_conf.ProxyCache)).Parse()
newConfig, err = parser.NewStringParser(string(nginx_conf.ProxyCache)).Parse()
}
if err != nil {
return nil
}
for _, dir := range newConfig.GetDirectives() {
addParam := dto.NginxParam{

View file

@ -1511,13 +1511,19 @@ func (w WebsiteService) OperateProxy(req request.WebsiteProxyConfig) (err error)
switch req.Operate {
case "create":
config = parser.NewStringParser(string(nginx_conf.Proxy)).Parse()
config, err = parser.NewStringParser(string(nginx_conf.Proxy)).Parse()
if err != nil {
return
}
case "edit":
par, err = parser.NewParser(includePath)
if err != nil {
return
}
config = par.Parse()
config, err = par.Parse()
if err != nil {
return
}
oldContent, err = fileOp.GetContent(includePath)
if err != nil {
return
@ -1533,6 +1539,7 @@ func (w WebsiteService) OperateProxy(req request.WebsiteProxyConfig) (err error)
_ = fileOp.Rename(backPath, includePath)
return updateNginxConfig(constant.NginxScopeServer, nil, &website)
}
config.FilePath = includePath
directives := config.Directives
location, ok := directives[0].(*components.Location)
@ -1610,7 +1617,10 @@ func (w WebsiteService) GetProxies(id uint) (res []request.WebsiteProxyConfig, e
return
}
proxyConfig.Content = string(content)
config = parser.NewStringParser(string(content)).Parse()
config, err = parser.NewStringParser(string(content)).Parse()
if err != nil {
return nil, err
}
directives := config.GetDirectives()
location, ok := directives[0].(*components.Location)
@ -2057,7 +2067,10 @@ func (w WebsiteService) OperateRedirect(req request.NginxRedirectReq) (err error
if err != nil {
return
}
config = oldPar.Parse()
config, err = oldPar.Parse()
if err != nil {
return
}
oldContent, err = fileOp.GetContent(includePath)
if err != nil {
return
@ -2198,7 +2211,10 @@ func (w WebsiteService) GetRedirect(id uint) (res []response.NginxRedirectConfig
return
}
redirectConfig.Content = string(content)
config = parser.NewStringParser(string(content)).Parse()
config, err = parser.NewStringParser(string(content)).Parse()
if err != nil {
return
}
dirs := config.GetDirectives()
if len(dirs) > 0 {

View file

@ -132,7 +132,10 @@ func createProxyFile(website *model.Website, runtime *model.Runtime) error {
return err
}
}
config := parser.NewStringParser(string(nginx_conf.Proxy)).Parse()
config, err := parser.NewStringParser(string(nginx_conf.Proxy)).Parse()
if err != nil {
return err
}
config.FilePath = filePath
directives := config.Directives
location, ok := directives[0].(*components.Location)
@ -208,7 +211,10 @@ func configDefaultNginx(website *model.Website, domains []model.WebsiteDomain, a
nginxFileName := website.Alias + ".conf"
configPath := path.Join(constant.AppInstallDir, constant.AppOpenresty, nginxInstall.Name, "conf", "conf.d", nginxFileName)
nginxContent := string(nginx_conf.WebsiteDefault)
config := parser.NewStringParser(nginxContent).Parse()
config, err := parser.NewStringParser(nginxContent).Parse()
if err != nil {
return err
}
servers := config.FindServers()
if len(servers) == 0 {
return errors.New("nginx config is not valid")
@ -872,7 +878,10 @@ func ChangeHSTSConfig(enable bool, nginxInstall model.AppInstall, website model.
if err != nil {
return err
}
config := par.Parse()
config, err := par.Parse()
if err != nil {
return err
}
config.FilePath = path
directives := config.Directives
location, ok := directives[0].(*components.Location)

View file

@ -1,9 +1,11 @@
package components
type Block struct {
Line int
Comment string
Directives []IDirective
Line int
Comment string
Directives []IDirective
IsLuaBlock bool
LiteralCode string
}
func (b *Block) GetDirectives() []IDirective {
@ -18,6 +20,10 @@ func (b *Block) GetLine() int {
return b.Line
}
func (b *Block) GetCodeBlock() string {
return b.LiteralCode
}
func (b *Block) FindDirectives(directiveName string) []IDirective {
directives := make([]IDirective, 0)
for _, directive := range b.GetDirectives() {

View file

@ -11,6 +11,10 @@ type Http struct {
Line int
}
func (h *Http) GetCodeBlock() string {
return ""
}
func (h *Http) GetComment() string {
return h.Comment
}

View file

@ -22,6 +22,10 @@ type Location struct {
Replaces map[string]string
}
func (l *Location) GetCodeBlock() string {
return ""
}
func NewLocation(directive IDirective) *Location {
location := &Location{
Modifier: "",

View file

@ -0,0 +1,120 @@
package components
import (
"fmt"
)
type LuaBlock struct {
Directives []IDirective
Name string
Comment string
LuaCode string
Line int
}
func NewLuaBlock(directive IDirective) (*LuaBlock, error) {
if block := directive.GetBlock(); block != nil {
lb := &LuaBlock{
Directives: []IDirective{},
Name: directive.GetName(),
LuaCode: block.GetCodeBlock(),
}
lb.Directives = append(lb.Directives, block.GetDirectives()...)
return lb, nil
}
return nil, fmt.Errorf("%s must have a block", directive.GetName())
}
func (lb *LuaBlock) GetName() string {
return lb.Name
}
func (lb *LuaBlock) GetParameters() []string {
return []string{}
}
func (lb *LuaBlock) GetDirectives() []IDirective {
directives := make([]IDirective, 0)
directives = append(directives, lb.Directives...)
return directives
}
func (lb *LuaBlock) FindDirectives(directiveName string) []IDirective {
directives := make([]IDirective, 0)
for _, directive := range lb.GetDirectives() {
if directive.GetName() == directiveName {
directives = append(directives, directive)
}
if directive.GetBlock() != nil {
directives = append(directives, directive.GetBlock().FindDirectives(directiveName)...)
}
}
return directives
}
func (lb *LuaBlock) GetCodeBlock() string {
return lb.LuaCode
}
func (lb *LuaBlock) GetBlock() IBlock {
return lb
}
func (lb *LuaBlock) GetComment() string {
return lb.Comment
}
func (lb *LuaBlock) RemoveDirective(key string, params []string) {
directives := lb.Directives
var newDirectives []IDirective
for _, dir := range directives {
if dir.GetName() == key {
if len(params) > 0 {
oldParams := dir.GetParameters()
if oldParams[0] == params[0] {
continue
}
} else {
continue
}
}
newDirectives = append(newDirectives, dir)
}
lb.Directives = newDirectives
}
func (lb *LuaBlock) UpdateDirective(key string, params []string) {
if key == "" || len(params) == 0 {
return
}
directives := lb.Directives
index := -1
for i, dir := range directives {
if dir.GetName() == key {
if IsRepeatKey(key) {
oldParams := dir.GetParameters()
if !(len(oldParams) > 0 && oldParams[0] == params[0]) {
continue
}
}
index = i
break
}
}
newDirective := &Directive{
Name: key,
Parameters: params,
}
if index > -1 {
directives[index] = newDirective
} else {
directives = append(directives, newDirective)
}
lb.Directives = directives
}
func (lb *LuaBlock) GetLine() int {
return lb.Line
}

View file

@ -11,6 +11,10 @@ type Server struct {
Line int
}
func (s *Server) GetCodeBlock() string {
return ""
}
func NewServer(directive IDirective) (*Server, error) {
server := &Server{}
if block := directive.GetBlock(); block != nil {

View file

@ -7,6 +7,7 @@ type IBlock interface {
UpdateDirective(name string, params []string)
GetComment() string
GetLine() int
GetCodeBlock() string
}
type IDirective interface {

View file

@ -12,6 +12,10 @@ type Upstream struct {
Line int
}
func (us *Upstream) GetCodeBlock() string {
return ""
}
func (us *Upstream) GetName() string {
return "upstream"
}

View file

@ -65,6 +65,21 @@ func DumpDirective(d components.IDirective, style *Style) string {
func DumpBlock(b components.IBlock, style *Style, startLine int) string {
var buf bytes.Buffer
if b.GetCodeBlock() != "" {
luaLines := strings.Split(b.GetCodeBlock(), "\n")
for i, line := range luaLines {
if strings.Replace(line, " ", "", -1) == "" {
continue
}
buf.WriteString(line)
if i != len(luaLines)-1 {
buf.WriteString("\n")
}
}
return buf.String()
}
line := startLine
if b.GetLine() > startLine {
for i := 0; i < b.GetLine()-startLine; i++ {

View file

@ -10,5 +10,5 @@ func GetConfig(path string) (*components.Config, error) {
if err != nil {
return nil, err
}
return p.Parse(), nil
return p.Parse()
}

View file

@ -18,6 +18,7 @@ const (
Comment
Illegal
Regex
LuaCode
)
var (

View file

@ -3,17 +3,18 @@ package parser
import (
"bufio"
"bytes"
"fmt"
"github.com/1Panel-dev/1Panel/backend/utils/nginx/parser/flag"
"io"
"strings"
)
type lexer struct {
reader *bufio.Reader
file string
line int
column int
Latest flag.Flag
reader *bufio.Reader
file string
line int
column int
inLuaBlock bool
Latest flag.Flag
}
func lex(content string) *lexer {
@ -45,6 +46,11 @@ func (s *lexer) scan() flag.Flag {
//}
func (s *lexer) getNextFlag() flag.Flag {
if s.inLuaBlock {
s.inLuaBlock = false
flag := s.scanLuaCode()
return flag
}
retoFlag:
ch := s.peek()
switch {
@ -56,6 +62,9 @@ retoFlag:
case ch == ';':
return s.NewToken(flag.Semicolon).Lit(string(s.read()))
case ch == '{':
if isLuaBlock(s.Latest) {
s.inLuaBlock = true
}
return s.NewToken(flag.BlockStart).Lit(string(s.read()))
case ch == '}':
return s.NewToken(flag.BlockEnd).Lit(string(s.read()))
@ -70,6 +79,35 @@ retoFlag:
}
}
func (s *lexer) scanLuaCode() flag.Flag {
ret := s.NewToken(flag.LuaCode)
stack := make([]rune, 0, 50)
code := strings.Builder{}
for {
ch := s.read()
if ch == rune(flag.EOF) {
panic("unexpected end of file while scanning a string, maybe an unclosed lua code?")
}
if ch == '#' {
code.WriteRune(ch)
code.WriteString(s.readUntil(isEndOfLine))
continue
} else if ch == '}' {
if len(stack) == 0 {
_ = s.reader.UnreadRune()
return ret.Lit(strings.TrimRight(strings.Trim(code.String(), "\n"), "\n "))
}
if stack[len(stack)-1] == '{' {
stack = stack[0 : len(stack)-1]
}
} else if ch == '{' {
stack = append(stack, ch)
}
code.WriteRune(ch)
}
}
func (s *lexer) peek() rune {
r, _, _ := s.reader.ReadRune()
_ = s.reader.UnreadRune()
@ -128,7 +166,7 @@ func (s *lexer) scanComment() flag.Flag {
func (s *lexer) scanQuotedString(delimiter rune) flag.Flag {
var buf bytes.Buffer
tok := s.NewToken(flag.QuotedString)
buf.WriteRune(s.read()) //consume delimiter
_, _ = buf.WriteRune(s.read())
for {
ch := s.read()
@ -136,29 +174,13 @@ func (s *lexer) scanQuotedString(delimiter rune) flag.Flag {
panic("unexpected end of file while scanning a string, maybe an unclosed quote?")
}
if ch == '\\' {
if needsEscape(s.peek(), delimiter) {
nextch := s.read()
switch nextch {
case 'n':
fmt.Println("n")
buf.WriteRune('\n')
case 'r':
fmt.Println("r")
buf.WriteRune('\r')
case 't':
fmt.Println("t")
buf.WriteRune('\t')
case '\\':
buf.WriteRune('\\')
default:
buf.WriteRune('\\')
buf.WriteRune(nextch)
}
continue
}
if ch == '\\' && (s.peek() == delimiter) {
buf.WriteRune(ch)
buf.WriteRune(s.read())
continue
}
buf.WriteRune(ch)
_, _ = buf.WriteRune(ch)
if ch == delimiter {
break
}
@ -168,7 +190,31 @@ func (s *lexer) scanQuotedString(delimiter rune) flag.Flag {
}
func (s *lexer) scanKeyword() flag.Flag {
return s.NewToken(flag.Keyword).Lit(s.readUntil(isKeywordTerminator))
var buf bytes.Buffer
tok := s.NewToken(flag.Keyword)
prev := s.read()
buf.WriteRune(prev)
for {
ch := s.peek()
if isSpace(ch) || isEOF(ch) || ch == ';' {
break
}
if ch == '{' {
if prev == '$' {
buf.WriteString(s.readUntil(func(r rune) bool {
return r == '}'
}))
buf.WriteRune(s.read()) //consume latest '}'
} else {
break
}
}
buf.WriteRune(s.read())
}
return tok.Lit(buf.String())
}
func (s *lexer) scanVariable() flag.Flag {
@ -198,10 +244,6 @@ func isKeywordTerminator(ch rune) bool {
return isSpace(ch) || isEndOfLine(ch) || ch == '{' || ch == ';'
}
func needsEscape(ch, delimiter rune) bool {
return ch == delimiter || ch == 'n' || ch == 't' || ch == '\\' || ch == 'r'
}
func isSpace(ch rune) bool {
return ch == ' ' || ch == '\t' || isEndOfLine(ch)
}
@ -213,3 +255,7 @@ func isEOF(ch rune) bool {
func isEndOfLine(ch rune) bool {
return ch == '\r' || ch == '\n'
}
func isLuaBlock(t flag.Flag) bool {
return t.Type == flag.Keyword && strings.HasSuffix(t.Literal, "_by_lua_block")
}

View file

@ -2,17 +2,19 @@ package parser
import (
"bufio"
"errors"
"fmt"
components "github.com/1Panel-dev/1Panel/backend/utils/nginx/components"
"github.com/1Panel-dev/1Panel/backend/utils/nginx/parser/flag"
"os"
"strings"
)
type Parser struct {
lexer *lexer
currentToken flag.Flag
followingToken flag.Flag
blockWrappers map[string]func(*components.Directive) components.IDirective
blockWrappers map[string]func(*components.Directive) (components.IDirective, error)
directiveWrappers map[string]func(*components.Directive) components.IDirective
}
@ -39,18 +41,21 @@ func NewParserFromLexer(lexer *lexer) *Parser {
parser.nextToken()
parser.nextToken()
parser.blockWrappers = map[string]func(*components.Directive) components.IDirective{
"http": func(directive *components.Directive) components.IDirective {
return parser.wrapHttp(directive)
parser.blockWrappers = map[string]func(*components.Directive) (components.IDirective, error){
"http": func(directive *components.Directive) (components.IDirective, error) {
return parser.wrapHttp(directive), nil
},
"server": func(directive *components.Directive) components.IDirective {
return parser.wrapServer(directive)
"server": func(directive *components.Directive) (components.IDirective, error) {
return parser.wrapServer(directive), nil
},
"location": func(directive *components.Directive) components.IDirective {
return parser.wrapLocation(directive)
"location": func(directive *components.Directive) (components.IDirective, error) {
return parser.wrapLocation(directive), nil
},
"upstream": func(directive *components.Directive) components.IDirective {
return parser.wrapUpstream(directive)
"upstream": func(directive *components.Directive) (components.IDirective, error) {
return parser.wrapUpstream(directive), nil
},
"_by_lua_block": func(directive *components.Directive) (components.IDirective, error) {
return parser.wrapLuaBlock(directive)
},
}
@ -76,14 +81,19 @@ func (p *Parser) followingTokenIs(t flag.Type) bool {
return p.followingToken.Type == t
}
func (p *Parser) Parse() *components.Config {
return &components.Config{
FilePath: p.lexer.file,
Block: p.parseBlock(),
func (p *Parser) Parse() (*components.Config, error) {
parsedBlock, err := p.parseBlock(false)
if err != nil {
return nil, err
}
c := &components.Config{
FilePath: p.lexer.file,
Block: parsedBlock,
}
return c, err
}
func (p *Parser) parseBlock() *components.Block {
func (p *Parser) parseBlock(inBlock bool) (*components.Block, error) {
context := &components.Block{
Comment: "",
Directives: make([]components.IDirective, 0),
@ -93,10 +103,22 @@ func (p *Parser) parseBlock() *components.Block {
parsingloop:
for {
switch {
case p.curTokenIs(flag.EOF) || p.curTokenIs(flag.BlockEnd):
case p.curTokenIs(flag.EOF):
if inBlock {
return nil, errors.New("unexpected eof in block")
}
break parsingloop
case p.curTokenIs(flag.BlockEnd):
break parsingloop
case p.curTokenIs(flag.LuaCode):
context.IsLuaBlock = true
context.LiteralCode = p.currentToken.Literal
case p.curTokenIs(flag.Keyword):
context.Directives = append(context.Directives, p.parseStatement())
s, err := p.parseStatement()
if err != nil {
return nil, err
}
context.Directives = append(context.Directives, s)
case p.curTokenIs(flag.Comment):
context.Directives = append(context.Directives, &components.Comment{
Detail: p.currentToken.Literal,
@ -106,10 +128,10 @@ parsingloop:
p.nextToken()
}
return context
return context, nil
}
func (p *Parser) parseStatement() components.IDirective {
func (p *Parser) parseStatement() (components.IDirective, error) {
d := &components.Directive{
Name: p.currentToken.Literal,
Line: p.currentToken.Line,
@ -121,30 +143,38 @@ func (p *Parser) parseStatement() components.IDirective {
if p.curTokenIs(flag.Semicolon) {
if dw, ok := p.directiveWrappers[d.Name]; ok {
return dw(d)
return dw(d), nil
}
if p.followingTokenIs(flag.Comment) && p.currentToken.Line == p.followingToken.Line {
d.Comment = p.followingToken.Literal
p.nextToken()
}
return d
return d, nil
}
if p.curTokenIs(flag.BlockStart) {
inLineComment := ""
if p.followingTokenIs(flag.Comment) && p.currentToken.Line == p.followingToken.Line {
inLineComment = p.followingToken.Literal
p.nextToken()
p.nextToken()
}
block := p.parseBlock()
block, err := p.parseBlock(false)
if err != nil {
return nil, err
}
block.Comment = inLineComment
d.Block = block
if strings.HasSuffix(d.Name, "_by_lua_block") {
return p.blockWrappers["_by_lua_block"](d)
}
if bw, ok := p.blockWrappers[d.Name]; ok {
return bw(d)
}
return d
return d, nil
}
panic(fmt.Errorf("unexpected token %s (%s) on line %d, column %d", p.currentToken.Type.String(), p.currentToken.Literal, p.currentToken.Line, p.currentToken.Column))
@ -169,6 +199,10 @@ func (p *Parser) wrapHttp(directive *components.Directive) *components.Http {
return h
}
func (p *Parser) wrapLuaBlock(directive *components.Directive) (*components.LuaBlock, error) {
return components.NewLuaBlock(directive)
}
func (p *Parser) parseUpstreamServer(directive *components.Directive) *components.UpstreamServer {
return components.NewUpstreamServer(directive)
}