higress
higress copied to clipboard
Add support of `try_files` usage
Why do you need it?
When serving some static files with app, users want to access files with or without suffix.
How could it be?
Implement one annotation witch support try_files
actions, like below:
higress.io/try_files: '$1 $1.html $1/index.html $1/'
.
When user access one path, try response content of some more paths, eg, when user access example.com/a
, response content using /a
, /a.html
,/a/index.html
, /a/
inorder, and only return non 200 status code when all these paths returns null.
Other related information
We will implement this functionality through plugins instead of annotations
This feature can be implemented by using routeCluster in the plugin and using it with httpcall. Interested developers are welcome to contribute.
reference: routeCluster:https://github.com/alibaba/higress/blob/50a219ed01881608d89170253b2b02f20710ee6f/plugins/wasm-go/pkg/wrapper/cluster_wrapper.go#L29 httpCall:https://github.com/alibaba/higress/blob/50a219ed01881608d89170253b2b02f20710ee6f/plugins/wasm-go/pkg/wrapper/http_wrapper.go#L75
may I have a try for this,write a go wasm plugin
@OnlyPiglet Are you finished the feature, i also have the need for it。
@haifzhu will finished this weekend, thanks a lot.
package main
import (
"errors"
"net/http"
"strings"
"github.com/alibaba/higress/plugins/wasm-go/pkg/wrapper"
"github.com/higress-group/proxy-wasm-go-sdk/proxywasm"
"github.com/higress-group/proxy-wasm-go-sdk/proxywasm/types"
"github.com/tidwall/gjson"
)
var defaultTimeout uint32 = 60000
func main() {
wrapper.SetCtx(
"try-paths",
wrapper.ParseConfigBy(parseConfig),
wrapper.ProcessRequestHeadersBy(onHttpRequestHeaders),
)
}
// 自定义插件配置
type TryPathsConfig struct {
host string
port int64
tryPaths []string
code int
client wrapper.HttpClient
}
func Client(json gjson.Result) (wrapper.HttpClient, error) {
serviceSource := json.Get("serviceSource").String()
serviceName := json.Get("serviceName").String()
servicePort := json.Get("servicePort").Int()
if serviceName == "" || servicePort == 0 {
return nil, errors.New("invalid service config")
}
switch serviceSource {
case "k8s":
namespace := json.Get("namespace").String()
return wrapper.NewClusterClient(wrapper.K8sCluster{
ServiceName: serviceName,
Namespace: namespace,
Port: servicePort,
}), nil
case "nacos":
namespace := json.Get("namespace").String()
return wrapper.NewClusterClient(wrapper.NacosCluster{
ServiceName: serviceName,
NamespaceID: namespace,
Port: servicePort,
}), nil
case "ip":
return wrapper.NewClusterClient(wrapper.StaticIpCluster{
ServiceName: serviceName,
Port: servicePort,
}), nil
case "dns":
domain := json.Get("domain").String()
return wrapper.NewClusterClient(wrapper.DnsCluster{
ServiceName: serviceName,
Port: servicePort,
Domain: domain,
}), nil
default:
return nil, errors.New("unknown service source: " + serviceSource)
}
}
func parseConfig(json gjson.Result, config *TryPathsConfig, log wrapper.Log) error {
// 解析出配置,更新到config中
for _, result := range json.Get("tryPaths").Array() {
config.tryPaths = append(config.tryPaths, result.String())
}
// code默认值为404
if json.Get("code").String() == "" {
config.code = http.StatusNotFound
} else {
config.code = int(json.Get("code").Int())
}
client, err := Client(json)
if err != nil {
return err
}
config.client = client
return nil
}
func convHttpHeadersToStruct(responseHeaders http.Header) [][2]string {
headerStruct := make([][2]string, len(responseHeaders))
i := 0
for key, values := range responseHeaders {
headerStruct[i][0] = key
headerStruct[i][1] = values[0]
i++
}
return headerStruct
}
func onHttpRequestHeaders(ctx wrapper.HttpContext, config TryPathsConfig, log wrapper.Log) types.Action {
path := ctx.Path()
log.Infof("onHttpRequestHeaders path: %s, config: %s", path, config)
for _, requestPath := range config.tryPaths {
requestPath = strings.Replace(requestPath, "$uri", path, -1)
config.client.Get(requestPath, nil,
func(statusCode int, responseHeaders http.Header, responseBody []byte) {
if statusCode != config.code {
proxywasm.SendHttpResponse(uint32(statusCode), convHttpHeadersToStruct(responseHeaders), responseBody, -1)
return
}
proxywasm.ResumeHttpRequest()
}, defaultTimeout)
return types.ActionPause
}
// 需要等待异步回调完成,返回Pause状态,可以被ResumeHttpRequest恢复
return types.ActionPause
}
主逻辑是不是这样的?
@haifzhu 有点问题,应该在回调里迭代处理下一个trypath
可以添加一个nginx的proxy_cache 缓存功能做请求容灾