diff --git a/pkg/webserver/apache/data.go b/pkg/webserver/apache/data.go index 40442f70..b9f3fa89 100644 --- a/pkg/webserver/apache/data.go +++ b/pkg/webserver/apache/data.go @@ -33,3 +33,149 @@ const DefaultVhostConf = ` ` + +// order 定义 Apache 指令的排序优先级 +var order = map[string]int{ + "Listen": 0, + "ServerName": 1, + + "ServerAlias": 10, + "ServerAdmin": 11, + + "DocumentRoot": 100, + "DirectoryIndex": 101, + "Options": 102, + "AllowOverride": 103, + "Require": 104, + "Order": 105, + "Allow": 106, + "Deny": 107, + + "LimitRequestBody": 200, + "LimitRequestFields": 201, + "LimitRequestFieldSize": 202, + "LimitRequestLine": 203, + "LimitXMLRequestBody": 204, + + "AuthType": 300, + "AuthName": 301, + "AuthUserFile": 302, + "AuthGroupFile": 303, + "AuthBasicProvider": 304, + + "SSLEngine": 400, + "SSLCertificateFile": 401, + "SSLCertificateKeyFile": 402, + "SSLCertificateChainFile": 403, + "SSLCACertificateFile": 404, + "SSLCACertificatePath": 405, + "SSLProtocol": 406, + "SSLCipherSuite": 407, + "SSLHonorCipherOrder": 408, + "SSLCompression": 409, + "SSLSessionCache": 410, + "SSLSessionCacheTimeout": 411, + "SSLSessionTickets": 412, + "SSLUseStapling": 413, + "SSLStaplingCache": 414, + "SSLStaplingResponderTimeout": 415, + "SSLStaplingReturnResponderErrors": 416, + "SSLInsecureRenegotiation": 417, + "SSLVerifyClient": 418, + "SSLVerifyDepth": 419, + "SSLOptions": 420, + + "Header": 500, + "RequestHeader": 501, + "SetEnvIf": 502, + "SetEnvIfNoCase": 503, + "SetEnv": 504, + "UnsetEnv": 505, + "PassEnv": 506, + "SetOutputFilter": 507, + "SetInputFilter": 508, + "AddOutputFilter": 509, + "AddInputFilter": 510, + "AddType": 511, + "AddHandler": 512, + "AddCharset": 513, + "AddEncoding": 514, + "AddLanguage": 515, + "DefaultType": 516, + "ForceType": 517, + "RemoveType": 518, + "RemoveHandler": 519, + "RemoveCharset": 520, + "RemoveEncoding": 521, + "RemoveLanguage": 522, + + "ProxyPass": 600, + "ProxyPassReverse": 601, + "ProxyPassMatch": 602, + "ProxyPassReverseCookieDomain": 603, + "ProxyPassReverseCookiePath": 604, + "ProxyPreserveHost": 605, + "ProxyRequests": 606, + "ProxyVia": 607, + "ProxyTimeout": 608, + "ProxyAddHeaders": 609, + "ProxySet": 610, + "BalancerMember": 611, + "ProxyPassInherit": 612, + "ProxyPassInterpolateEnv": 613, + + "RewriteEngine": 700, + "RewriteBase": 701, + "RewriteCond": 702, + "RewriteRule": 703, + "RewriteMap": 704, + "RewriteOptions": 705, + + "Redirect": 800, + "RedirectMatch": 801, + "RedirectTemp": 802, + "RedirectPermanent": 803, + + "Alias": 900, + "AliasMatch": 901, + "ScriptAlias": 902, + "ScriptAliasMatch": 903, + + "ErrorDocument": 1000, + + "ExpiresActive": 1100, + "ExpiresDefault": 1101, + "ExpiresByType": 1102, + "DeflateCompressionLevel": 1103, + "DeflateMemLevel": 1104, + "DeflateWindowSize": 1105, + "DeflateBufferSize": 1106, + "DeflateFilterNote": 1107, + "AddOutputFilterByType": 1108, + + "PHPIniDir": 1200, + "SetHandler": 1201, + + "Directory": 1300, + "DirectoryMatch": 1301, + "Files": 1302, + "FilesMatch": 1303, + "Location": 1304, + "LocationMatch": 1305, + "If": 1306, + "IfDefine": 1307, + "IfModule": 1308, + "Else": 1309, + "ElseIf": 1310, + "Proxy": 1311, + "ProxyMatch": 1312, + + "Include": 1400, + "IncludeOptional": 1401, + + "ErrorLog": 1500, + "CustomLog": 1501, + "LogLevel": 1502, + "LogFormat": 1503, + "TransferLog": 1504, +} diff --git a/pkg/webserver/apache/export.go b/pkg/webserver/apache/export.go index 255bbd1f..cb1910c7 100644 --- a/pkg/webserver/apache/export.go +++ b/pkg/webserver/apache/export.go @@ -2,6 +2,7 @@ package apache import ( "fmt" + "slices" "sort" "strings" ) @@ -32,7 +33,7 @@ func DefaultExportOptions() *ExportOptions { return &ExportOptions{ IndentStyle: "spaces", IndentSize: 4, - SortDirectives: false, + SortDirectives: true, IncludeComments: true, PreserveEmptyLines: true, FormatStyle: "standard", @@ -79,8 +80,10 @@ func (c *Config) ExportWithOptions(options *ExportOptions) string { }) } - // 如果不需要保持原始顺序,按行号排序 - if !options.SortDirectives { + // 排序:如果需要语义排序则按 order 排序,否则按行号排序 + if options.SortDirectives { + sortDirectivesSlice(items, order) + } else { sort.Slice(items, func(i, j int) bool { return items[i].line < items[j].line }) @@ -186,8 +189,10 @@ func (v *VirtualHost) ExportWithOptions(options *ExportOptions, indent int) stri }) } - // 排序 - if !options.SortDirectives { + // 排序:如果需要语义排序则按 order 排序,否则按行号排序 + if options.SortDirectives { + sortDirectivesSlice(items, order) + } else { sort.Slice(items, func(i, j int) bool { return items[i].line < items[j].line }) @@ -268,8 +273,10 @@ func (b *Block) ExportWithOptions(options *ExportOptions, indent int) string { } } - // 按行号排序 + // 排序:如果需要语义排序则按 order 排序,否则按行号排序 if options.SortDirectives { + sortDirectivesSlice(allItems, order) + } else { sort.Slice(allItems, func(i, j int) bool { return allItems[i].line < allItems[j].line }) @@ -345,3 +352,67 @@ func shouldAddEmptyLine(current, next exportItem, options *ExportOptions) bool { return false } + +// sortDirectivesSlice 对指令切片进行语义排序 +func sortDirectivesSlice(items []exportItem, orderIndex map[string]int) { + slices.SortFunc(items, func(a, b exportItem) int { + // 跳过注释,注释保持原有位置 + if a.typ == "comment" || b.typ == "comment" { + return a.line - b.line + } + + var aName, bName string + switch a.typ { + case "directive": + if dir, ok := a.item.(*Directive); ok { + aName = dir.Name + } + case "virtualhost": + if vhost, ok := a.item.(*VirtualHost); ok { + aName = vhost.Name + } + } + + switch b.typ { + case "directive": + if dir, ok := b.item.(*Directive); ok { + bName = dir.Name + } + case "virtualhost": + if vhost, ok := b.item.(*VirtualHost); ok { + bName = vhost.Name + } + } + + // 按照 order 优先级排序 + if orderIndex[aName] != orderIndex[bName] { + return orderIndex[aName] - orderIndex[bName] + } + + // 优先级相同时,按照参数排序 + var aArgs, bArgs []string + switch a.typ { + case "directive": + if dir, ok := a.item.(*Directive); ok { + aArgs = dir.Args + } + case "virtualhost": + if vhost, ok := a.item.(*VirtualHost); ok { + aArgs = vhost.Args + } + } + + switch b.typ { + case "directive": + if dir, ok := b.item.(*Directive); ok { + bArgs = dir.Args + } + case "virtualhost": + if vhost, ok := b.item.(*VirtualHost); ok { + bArgs = vhost.Args + } + } + + return slices.Compare(aArgs, bArgs) + }) +} diff --git a/pkg/webserver/nginx/data.go b/pkg/webserver/nginx/data.go index 1c001308..1fc71732 100644 --- a/pkg/webserver/nginx/data.go +++ b/pkg/webserver/nginx/data.go @@ -44,3 +44,151 @@ server { error_log /opt/ace/sites/default/log/error.log; } ` + +// order 定义 Nginx 指令的排序优先级 +var order = map[string]int{ + "listen": 0, + "server_name": 1, + + "root": 10, + "index": 11, + "try_files": 12, + "charset": 13, + "autoindex": 14, + + "client_max_body_size": 100, + "client_body_buffer_size": 101, + "limit_except": 102, + "limit_req_zone": 103, + "limit_req": 104, + "limit_conn_zone": 105, + "limit_conn": 106, + "allow": 107, + "deny": 108, + "auth_basic": 109, + "auth_basic_user_file": 110, + + "ssl": 200, + "ssl_certificate": 201, + "ssl_certificate_key": 202, + "ssl_session_timeout": 203, + "ssl_session_cache": 204, + "ssl_session_tickets": 205, + "ssl_protocols": 206, + "ssl_ciphers": 207, + "ssl_prefer_server_ciphers": 208, + "ssl_early_data": 209, + "ssl_dhparam": 210, + "ssl_stapling": 211, + "ssl_stapling_verify": 212, + "ssl_trusted_certificate": 213, + + "resolver": 300, + "resolver_timeout": 301, + + "proxy_pass": 400, + "proxy_redirect": 401, + "proxy_set_header": 402, + "proxy_hide_header": 403, + "proxy_pass_header": 404, + "proxy_http_version": 405, + "proxy_method": 406, + "proxy_headers_hash_max_size": 407, + "proxy_headers_hash_bucket_size": 408, + "proxy_buffering": 409, + "proxy_buffer_size": 410, + "proxy_buffers": 411, + "proxy_busy_buffers_size": 412, + "proxy_max_temp_file_size": 413, + "proxy_read_timeout": 414, + "proxy_send_timeout": 415, + "proxy_connect_timeout": 416, + "proxy_next_upstream": 417, + "proxy_next_upstream_tries": 418, + "proxy_next_upstream_timeout": 419, + "proxy_no_cache": 420, + "proxy_cache": 421, + "proxy_cache_key": 422, + "proxy_cache_valid": 423, + "proxy_cache_bypass": 424, + "proxy_cache_use_stale": 425, + "proxy_cache_lock": 426, + "proxy_cache_lock_timeout": 427, + "proxy_cache_background_update": 428, + "proxy_cache_min_uses": 429, + "proxy_ignore_client_abort": 430, + "proxy_intercept_errors": 431, + "proxy_ssl_server_name": 432, + "proxy_ssl_name": 433, + "proxy_ssl_protocols": 434, + "proxy_ssl_ciphers": 435, + "proxy_ssl_verify": 436, + "proxy_ssl_verify_depth": 437, + "proxy_ssl_trusted_certificate": 438, + + "fastcgi_pass": 500, + "fastcgi_index": 501, + "fastcgi_param": 502, + "fastcgi_split_path_info": 503, + "fastcgi_buffers": 504, + "fastcgi_buffer_size": 505, + "fastcgi_busy_buffers_size": 506, + "fastcgi_temp_file_write_size": 507, + "fastcgi_read_timeout": 508, + "fastcgi_send_timeout": 509, + "fastcgi_connect_timeout": 510, + "fastcgi_intercept_errors": 511, + + "uwsgi_pass": 600, + "uwsgi_param": 601, + "uwsgi_read_timeout": 602, + "uwsgi_send_timeout": 603, + "uwsgi_connect_timeout": 604, + + "grpc_pass": 700, + "grpc_read_timeout": 701, + "grpc_send_timeout": 702, + + "proxy_cache_path": 800, + "fastcgi_cache_path": 801, + "uwsgi_cache_path": 802, + "proxy_temp_path": 803, + "fastcgi_temp_path": 804, + "uwsgi_temp_path": 805, + + "gzip": 900, + "gzip_comp_level": 901, + "gzip_min_length": 902, + "gzip_types": 903, + "gzip_buffers": 904, + "gzip_proxied": 905, + "gzip_disable": 906, + "gzip_vary": 907, + + "brotli": 910, + "brotli_comp_level": 911, + "brotli_min_length": 912, + "brotli_types": 913, + + "zstd": 920, + "zstd_comp_level": 921, + "zstd_min_length": 922, + "zstd_types": 923, + "zstd_static": 924, + + "add_header": 1000, + "expires": 1001, + + "rewrite": 1100, + "return": 1101, + + "error_page": 1200, + + "if": 1300, + "location": 1301, + + "include": 1400, + + "access_log": 1500, + "error_log": 1501, +} diff --git a/pkg/webserver/nginx/parser.go b/pkg/webserver/nginx/parser.go index 9671f102..dddd3ded 100644 --- a/pkg/webserver/nginx/parser.go +++ b/pkg/webserver/nginx/parser.go @@ -4,6 +4,7 @@ import ( "errors" "fmt" "os" + "slices" "strings" "github.com/tufanbarisyildirim/gonginx/config" @@ -198,6 +199,7 @@ func (p *Parser) Dump() string { // Save 保存配置到文件 func (p *Parser) Save() error { + p.sortDirectives(p.cfg.Directives, order) content := p.Dump() if err := os.WriteFile(p.cfgPath, []byte(content), 0644); err != nil { return fmt.Errorf("failed to save config file: %w", err) @@ -211,6 +213,21 @@ func (p *Parser) SetConfigPath(path string) { p.cfgPath = path } +func (p *Parser) sortDirectives(directives []config.IDirective, orderIndex map[string]int) { + slices.SortFunc(directives, func(a config.IDirective, b config.IDirective) int { + if orderIndex[a.GetName()] != orderIndex[b.GetName()] { + return orderIndex[a.GetName()] - orderIndex[b.GetName()] + } + return slices.Compare(p.parameters2Slices(a.GetParameters()), p.parameters2Slices(b.GetParameters())) + }) + + for _, directive := range directives { + if block, ok := directive.GetBlock().(*config.Block); ok { + p.sortDirectives(block.Directives, orderIndex) + } + } +} + func (p *Parser) slices2Parameters(slices []string) []config.Parameter { var parameters []config.Parameter for _, slice := range slices {