Compare commits
78 Commits
8115f7b410
...
v0.59.0
Author | SHA1 | Date | |
---|---|---|---|
|
243ca994e0 | ||
|
b4d5d8c756 | ||
|
c6f9d8d403 | ||
|
939c490768 | ||
|
f390e4a401 | ||
|
e649692217 | ||
|
77990c31ef | ||
|
e680acf42d | ||
|
522e2c94c1 | ||
|
301515d2e8 | ||
|
f0442d0cd5 | ||
|
9ced717d69 | ||
|
4e8e9e1dec | ||
|
92cb0b30c2 | ||
|
e81b36c5ba | ||
|
d0d396becb | ||
|
ee3892798d | ||
|
8f23733f47 | ||
|
5a6d9f60c2 | ||
|
a5b7abfc8b | ||
|
1e650ea9a7 | ||
|
d689f0fc53 | ||
|
d505ecb473 | ||
|
2b83436a97 | ||
|
051299ec25 | ||
|
44985f574d | ||
|
c9ca9353cf | ||
|
31fa3f021a | ||
|
2d3af8a108 | ||
|
466d69eae0 | ||
|
7c8cbeb250 | ||
|
4fd6301577 | ||
|
53626b370c | ||
|
4fd800bc48 | ||
|
0d6d968fe8 | ||
|
8fb99ef7a9 | ||
|
88e74ff24d | ||
|
534dc99d55 | ||
|
595aba5a9b | ||
|
a4189ba474 | ||
|
9ec84f8143 | ||
|
8ab474cc97 | ||
|
a301046f3d | ||
|
8888610d83 | ||
|
fe5fb0326b | ||
|
eb1e19a821 | ||
|
10f2620131 | ||
|
ce677820c6 | ||
|
88fcc079e8 | ||
|
2dab5d0bca | ||
|
143750901e | ||
|
997d406ec2 | ||
|
cfd1a3128a | ||
|
57577ea044 | ||
|
c5c79e4148 | ||
|
55da58eca4 | ||
|
76a1efccd9 | ||
|
980f084ad1 | ||
|
3bf1eb8565 | ||
|
b2ae433e18 | ||
|
aa0a41ee4e | ||
|
1ea1530b36 | ||
|
e0c45a1aca | ||
|
813c45f5c2 | ||
|
aa74dc4646 | ||
|
2406ecdfea | ||
|
8668fef136 | ||
|
ea62bc5a34 | ||
|
23bb76397a | ||
|
487c8d7c29 | ||
|
f480160e2d | ||
|
30c246c488 | ||
|
75f3bce04d | ||
|
adc3adc13b | ||
|
e62d9a5242 | ||
|
134a46c00b | ||
|
ae08811636 | ||
|
6451583e60 |
4
.github/workflows/build-and-push-image.yml
vendored
@@ -2,7 +2,7 @@ name: Build Image and Publish to Dockerhub & GPR
|
||||
|
||||
on:
|
||||
release:
|
||||
types: [ created ]
|
||||
types: [ published ]
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
tag:
|
||||
@@ -61,7 +61,7 @@ jobs:
|
||||
echo "TAG_FRPS_GPR=ghcr.io/fatedier/frps:${{ env.TAG_NAME }}" >> $GITHUB_ENV
|
||||
|
||||
- name: Build and push frpc
|
||||
uses: docker/build-push-action@v4
|
||||
uses: docker/build-push-action@v5
|
||||
with:
|
||||
context: .
|
||||
file: ./dockerfiles/Dockerfile-for-frpc
|
||||
|
26
README.md
@@ -10,12 +10,23 @@
|
||||
<h3 align="center">Gold Sponsors</h3>
|
||||
<!--gold sponsors start-->
|
||||
<p align="center">
|
||||
<a href="https://workos.com/?utm_campaign=github_repo&utm_medium=referral&utm_content=frp&utm_source=github" target="_blank">
|
||||
<img width="350px" src="https://raw.githubusercontent.com/fatedier/frp/dev/doc/pic/sponsor_workos.png">
|
||||
<a href="https://lokal.so/?utm_campaign=github_repo&utm_medium=referral&utm_content=frp&utm_source=github" target="_blank">
|
||||
<img width="420px" src="https://raw.githubusercontent.com/fatedier/frp/dev/doc/pic/sponsor_lokal.png">
|
||||
</a>
|
||||
<a> </a>
|
||||
</p>
|
||||
<p align="center">
|
||||
<a href="https://workos.com/?utm_campaign=github_repo&utm_medium=referral&utm_content=frp&utm_source=github" target="_blank">
|
||||
<img width="420px" src="https://raw.githubusercontent.com/fatedier/frp/dev/doc/pic/sponsor_workos.png">
|
||||
</a>
|
||||
</p>
|
||||
<p align="center">
|
||||
<a href="https://github.com/daytonaio/daytona" target="_blank">
|
||||
<img width="360px" src="https://raw.githubusercontent.com/fatedier/frp/dev/doc/pic/sponsor_daytona.png">
|
||||
<img width="420px" src="https://raw.githubusercontent.com/fatedier/frp/dev/doc/pic/sponsor_daytona.png">
|
||||
</a>
|
||||
</p>
|
||||
<p align="center">
|
||||
<a href="https://github.com/beclab/terminus" target="_blank">
|
||||
<img width="420px" src="https://raw.githubusercontent.com/fatedier/frp/dev/doc/pic/sponsor_terminusos.jpeg">
|
||||
</a>
|
||||
</p>
|
||||
<!--gold sponsors end-->
|
||||
@@ -804,7 +815,7 @@ You can disable this feature by modify `frps.toml` and `frpc.toml`:
|
||||
|
||||
```toml
|
||||
# frps.toml and frpc.toml, must be same
|
||||
tcpMux = false
|
||||
transport.tcpMux = false
|
||||
```
|
||||
|
||||
### Support KCP Protocol
|
||||
@@ -983,7 +994,7 @@ The HTTP request will have the `Host` header rewritten to `Host: dev.example.com
|
||||
|
||||
### Setting other HTTP Headers
|
||||
|
||||
Similar to `Host`, You can override other HTTP request headers with proxy type `http`.
|
||||
Similar to `Host`, You can override other HTTP request and response headers with proxy type `http`.
|
||||
|
||||
```toml
|
||||
# frpc.toml
|
||||
@@ -995,9 +1006,10 @@ localPort = 80
|
||||
customDomains = ["test.example.com"]
|
||||
hostHeaderRewrite = "dev.example.com"
|
||||
requestHeaders.set.x-from-where = "frp"
|
||||
responseHeaders.set.foo = "bar"
|
||||
```
|
||||
|
||||
In this example, it will set header `x-from-where: frp` in the HTTP request.
|
||||
In this example, it will set header `x-from-where: frp` in the HTTP request and `foo: bar` in the HTTP response.
|
||||
|
||||
### Get Real IP
|
||||
|
||||
|
19
README_zh.md
@@ -12,12 +12,23 @@ frp 是一个专注于内网穿透的高性能的反向代理应用,支持 TCP
|
||||
<h3 align="center">Gold Sponsors</h3>
|
||||
<!--gold sponsors start-->
|
||||
<p align="center">
|
||||
<a href="https://workos.com/?utm_campaign=github_repo&utm_medium=referral&utm_content=frp&utm_source=github" target="_blank">
|
||||
<img width="350px" src="https://raw.githubusercontent.com/fatedier/frp/dev/doc/pic/sponsor_workos.png">
|
||||
<a href="https://lokal.so/?utm_campaign=github_repo&utm_medium=referral&utm_content=frp&utm_source=github" target="_blank">
|
||||
<img width="420px" src="https://raw.githubusercontent.com/fatedier/frp/dev/doc/pic/sponsor_lokal.png">
|
||||
</a>
|
||||
<a> </a>
|
||||
</p>
|
||||
<p align="center">
|
||||
<a href="https://workos.com/?utm_campaign=github_repo&utm_medium=referral&utm_content=frp&utm_source=github" target="_blank">
|
||||
<img width="420px" src="https://raw.githubusercontent.com/fatedier/frp/dev/doc/pic/sponsor_workos.png">
|
||||
</a>
|
||||
</p>
|
||||
<p align="center">
|
||||
<a href="https://github.com/daytonaio/daytona" target="_blank">
|
||||
<img width="360px" src="https://raw.githubusercontent.com/fatedier/frp/dev/doc/pic/sponsor_daytona.png">
|
||||
<img width="420px" src="https://raw.githubusercontent.com/fatedier/frp/dev/doc/pic/sponsor_daytona.png">
|
||||
</a>
|
||||
</p>
|
||||
<p align="center">
|
||||
<a href="https://github.com/beclab/terminus" target="_blank">
|
||||
<img width="420px" src="https://raw.githubusercontent.com/fatedier/frp/dev/doc/pic/sponsor_terminusos.jpeg">
|
||||
</a>
|
||||
</p>
|
||||
<!--gold sponsors end-->
|
||||
|
@@ -1,7 +1,8 @@
|
||||
### Features
|
||||
|
||||
* Show tcpmux proxies on the frps dashboard.
|
||||
* Added a new plugin "http2http" which allows forwarding HTTP requests to another HTTP server, supporting options like local address binding, host header rewrite, and custom request headers.
|
||||
* Added `enableHTTP2` option to control whether to enable HTTP/2 in plugin https2http and https2https, default is true.
|
||||
|
||||
### Fixes
|
||||
### Changes
|
||||
|
||||
* When an HTTP proxy request times out, it returns 504 instead of 404 now.
|
||||
* Plugin https2http & https2https: return 421 `Misdirected Request` if host not match sni.
|
||||
|
@@ -20,8 +20,6 @@ import (
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/samber/lo"
|
||||
|
||||
"github.com/fatedier/frp/client/proxy"
|
||||
"github.com/fatedier/frp/client/visitor"
|
||||
"github.com/fatedier/frp/pkg/auth"
|
||||
@@ -236,10 +234,8 @@ func (ctl *Control) registerMsgHandlers() {
|
||||
func (ctl *Control) heartbeatWorker() {
|
||||
xl := ctl.xl
|
||||
|
||||
// TODO(fatedier): Change default value of HeartbeatInterval to -1 if tcpmux is enabled.
|
||||
// Users can still enable heartbeat feature by setting HeartbeatInterval to a positive value.
|
||||
if ctl.sessionCtx.Common.Transport.HeartbeatInterval > 0 {
|
||||
// send heartbeat to server
|
||||
// Send heartbeat to server.
|
||||
sendHeartBeat := func() (bool, error) {
|
||||
xl.Debugf("send heartbeat to server")
|
||||
pingMsg := &msg.Ping{}
|
||||
@@ -263,10 +259,8 @@ func (ctl *Control) heartbeatWorker() {
|
||||
)
|
||||
}
|
||||
|
||||
// Check heartbeat timeout only if TCPMux is not enabled and users don't disable heartbeat feature.
|
||||
if ctl.sessionCtx.Common.Transport.HeartbeatInterval > 0 && ctl.sessionCtx.Common.Transport.HeartbeatTimeout > 0 &&
|
||||
!lo.FromPtr(ctl.sessionCtx.Common.Transport.TCPMux) {
|
||||
|
||||
// Check heartbeat timeout.
|
||||
if ctl.sessionCtx.Common.Transport.HeartbeatInterval > 0 && ctl.sessionCtx.Common.Transport.HeartbeatTimeout > 0 {
|
||||
go wait.Until(func() {
|
||||
if time.Since(ctl.lastPong.Load().(time.Time)) > time.Duration(ctl.sessionCtx.Common.Transport.HeartbeatTimeout)*time.Second {
|
||||
xl.Warnf("heartbeat timeout")
|
||||
|
@@ -76,7 +76,7 @@ transport.poolCount = 5
|
||||
|
||||
# Specify keep alive interval for tcp mux.
|
||||
# only valid if tcpMux is enabled.
|
||||
# transport.tcpMuxKeepaliveInterval = 60
|
||||
# transport.tcpMuxKeepaliveInterval = 30
|
||||
|
||||
# Communication protocol used to connect to server
|
||||
# supports tcp, kcp, quic, websocket and wss now, default is tcp
|
||||
@@ -209,6 +209,7 @@ locations = ["/", "/pic"]
|
||||
# routeByHTTPUser = abc
|
||||
hostHeaderRewrite = "example.com"
|
||||
requestHeaders.set.x-from-where = "frp"
|
||||
responseHeaders.set.foo = "bar"
|
||||
healthCheck.type = "http"
|
||||
# frpc will send a GET http request '/status' to local http service
|
||||
# http service is alive when it return 2xx http response code
|
||||
@@ -314,6 +315,16 @@ localAddr = "127.0.0.1:443"
|
||||
hostHeaderRewrite = "127.0.0.1"
|
||||
requestHeaders.set.x-from-where = "frp"
|
||||
|
||||
[[proxies]]
|
||||
name = "plugin_http2http"
|
||||
type = "tcp"
|
||||
remotePort = 6007
|
||||
[proxies.plugin]
|
||||
type = "http2http"
|
||||
localAddr = "127.0.0.1:80"
|
||||
hostHeaderRewrite = "127.0.0.1"
|
||||
requestHeaders.set.x-from-where = "frp"
|
||||
|
||||
[[proxies]]
|
||||
name = "secret_tcp"
|
||||
# If the type is secret tcp, remotePort is useless
|
||||
|
@@ -34,7 +34,7 @@ transport.maxPoolCount = 5
|
||||
|
||||
# Specify keep alive interval for tcp mux.
|
||||
# only valid if tcpMux is true.
|
||||
# transport.tcpMuxKeepaliveInterval = 60
|
||||
# transport.tcpMuxKeepaliveInterval = 30
|
||||
|
||||
# tcpKeepalive specifies the interval between keep-alive probes for an active network connection between frpc and frps.
|
||||
# If negative, keep-alive probes are disabled.
|
||||
|
Before Width: | Height: | Size: 27 KiB |
Before Width: | Height: | Size: 56 KiB After Width: | Height: | Size: 41 KiB |
Before Width: | Height: | Size: 41 KiB |
BIN
doc/pic/sponsor_lokal.png
Normal file
After Width: | Height: | Size: 55 KiB |
Before Width: | Height: | Size: 12 KiB |
BIN
doc/pic/sponsor_terminusos.jpeg
Normal file
After Width: | Height: | Size: 31 KiB |
@@ -345,35 +345,19 @@ func copySection(source, target *ini.Section) {
|
||||
}
|
||||
|
||||
// GetDefaultClientConf returns a client configuration with default values.
|
||||
// Note: Some default values here will be set to empty and will be converted to them
|
||||
// new configuration through the 'Complete' function to set them as the default
|
||||
// values of the new configuration.
|
||||
func GetDefaultClientConf() ClientCommonConf {
|
||||
return ClientCommonConf{
|
||||
ClientConfig: legacyauth.GetDefaultClientConf(),
|
||||
ServerAddr: "0.0.0.0",
|
||||
ServerPort: 7000,
|
||||
NatHoleSTUNServer: "stun.easyvoip.com:3478",
|
||||
DialServerTimeout: 10,
|
||||
DialServerKeepAlive: 7200,
|
||||
HTTPProxy: os.Getenv("http_proxy"),
|
||||
LogFile: "console",
|
||||
LogWay: "console",
|
||||
LogLevel: "info",
|
||||
LogMaxDays: 3,
|
||||
AdminAddr: "127.0.0.1",
|
||||
PoolCount: 1,
|
||||
TCPMux: true,
|
||||
TCPMuxKeepaliveInterval: 60,
|
||||
LoginFailExit: true,
|
||||
Start: make([]string, 0),
|
||||
Protocol: "tcp",
|
||||
QUICKeepalivePeriod: 10,
|
||||
QUICMaxIdleTimeout: 30,
|
||||
QUICMaxIncomingStreams: 100000,
|
||||
Start: make([]string, 0),
|
||||
TLSEnable: true,
|
||||
DisableCustomTLSFirstByte: true,
|
||||
HeartbeatInterval: 30,
|
||||
HeartbeatTimeout: 90,
|
||||
Metas: make(map[string]string),
|
||||
UDPPacketSize: 1500,
|
||||
IncludeConfigFiles: make([]string, 0),
|
||||
}
|
||||
}
|
||||
|
@@ -200,34 +200,20 @@ type ServerCommonConf struct {
|
||||
NatHoleAnalysisDataReserveHours int64 `ini:"nat_hole_analysis_data_reserve_hours" json:"nat_hole_analysis_data_reserve_hours"`
|
||||
}
|
||||
|
||||
// GetDefaultServerConf returns a server configuration with reasonable
|
||||
// defaults.
|
||||
// GetDefaultServerConf returns a server configuration with reasonable defaults.
|
||||
// Note: Some default values here will be set to empty and will be converted to them
|
||||
// new configuration through the 'Complete' function to set them as the default
|
||||
// values of the new configuration.
|
||||
func GetDefaultServerConf() ServerCommonConf {
|
||||
return ServerCommonConf{
|
||||
ServerConfig: legacyauth.GetDefaultServerConf(),
|
||||
BindAddr: "0.0.0.0",
|
||||
BindPort: 7000,
|
||||
QUICKeepalivePeriod: 10,
|
||||
QUICMaxIdleTimeout: 30,
|
||||
QUICMaxIncomingStreams: 100000,
|
||||
VhostHTTPTimeout: 60,
|
||||
DashboardAddr: "0.0.0.0",
|
||||
LogFile: "console",
|
||||
LogWay: "console",
|
||||
LogLevel: "info",
|
||||
LogMaxDays: 3,
|
||||
DetailedErrorsToClient: true,
|
||||
TCPMux: true,
|
||||
TCPMuxKeepaliveInterval: 60,
|
||||
TCPKeepAlive: 7200,
|
||||
AllowPorts: make(map[int]struct{}),
|
||||
MaxPoolCount: 5,
|
||||
MaxPortsPerClient: 0,
|
||||
HeartbeatTimeout: 90,
|
||||
UserConnTimeout: 10,
|
||||
HTTPPlugins: make(map[string]HTTPPluginOptions),
|
||||
UDPPacketSize: 1500,
|
||||
NatHoleAnalysisDataReserveHours: 7 * 24,
|
||||
ServerConfig: legacyauth.GetDefaultServerConf(),
|
||||
DashboardAddr: "0.0.0.0",
|
||||
LogFile: "console",
|
||||
LogWay: "console",
|
||||
DetailedErrorsToClient: true,
|
||||
TCPMux: true,
|
||||
AllowPorts: make(map[int]struct{}),
|
||||
HTTPPlugins: make(map[string]HTTPPluginOptions),
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -135,9 +135,15 @@ func (c *ClientTransportConfig) Complete() {
|
||||
c.ProxyURL = util.EmptyOr(c.ProxyURL, os.Getenv("http_proxy"))
|
||||
c.PoolCount = util.EmptyOr(c.PoolCount, 1)
|
||||
c.TCPMux = util.EmptyOr(c.TCPMux, lo.ToPtr(true))
|
||||
c.TCPMuxKeepaliveInterval = util.EmptyOr(c.TCPMuxKeepaliveInterval, 60)
|
||||
c.HeartbeatInterval = util.EmptyOr(c.HeartbeatInterval, 30)
|
||||
c.HeartbeatTimeout = util.EmptyOr(c.HeartbeatTimeout, 90)
|
||||
c.TCPMuxKeepaliveInterval = util.EmptyOr(c.TCPMuxKeepaliveInterval, 30)
|
||||
if lo.FromPtr(c.TCPMux) {
|
||||
// If TCPMux is enabled, heartbeat of application layer is unnecessary because we can rely on heartbeat in tcpmux.
|
||||
c.HeartbeatInterval = util.EmptyOr(c.HeartbeatInterval, -1)
|
||||
c.HeartbeatTimeout = util.EmptyOr(c.HeartbeatTimeout, -1)
|
||||
} else {
|
||||
c.HeartbeatInterval = util.EmptyOr(c.HeartbeatInterval, 30)
|
||||
c.HeartbeatTimeout = util.EmptyOr(c.HeartbeatTimeout, 90)
|
||||
}
|
||||
c.QUIC.Complete()
|
||||
c.TLS.Complete()
|
||||
}
|
||||
|
@@ -17,11 +17,18 @@ package v1
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"reflect"
|
||||
|
||||
"github.com/samber/lo"
|
||||
|
||||
"github.com/fatedier/frp/pkg/util/util"
|
||||
)
|
||||
|
||||
type ClientPluginOptions interface{}
|
||||
type ClientPluginOptions interface {
|
||||
Complete()
|
||||
}
|
||||
|
||||
type TypedClientPluginOptions struct {
|
||||
Type string `json:"type"`
|
||||
@@ -42,7 +49,7 @@ func (c *TypedClientPluginOptions) UnmarshalJSON(b []byte) error {
|
||||
|
||||
c.Type = typeStruct.Type
|
||||
if c.Type == "" {
|
||||
return nil
|
||||
return errors.New("plugin type is empty")
|
||||
}
|
||||
|
||||
v, ok := clientPluginOptionsTypeMap[typeStruct.Type]
|
||||
@@ -63,11 +70,16 @@ func (c *TypedClientPluginOptions) UnmarshalJSON(b []byte) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *TypedClientPluginOptions) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal(c.ClientPluginOptions)
|
||||
}
|
||||
|
||||
const (
|
||||
PluginHTTP2HTTPS = "http2https"
|
||||
PluginHTTPProxy = "http_proxy"
|
||||
PluginHTTPS2HTTP = "https2http"
|
||||
PluginHTTPS2HTTPS = "https2https"
|
||||
PluginHTTP2HTTP = "http2http"
|
||||
PluginSocks5 = "socks5"
|
||||
PluginStaticFile = "static_file"
|
||||
PluginUnixDomainSocket = "unix_domain_socket"
|
||||
@@ -78,6 +90,7 @@ var clientPluginOptionsTypeMap = map[string]reflect.Type{
|
||||
PluginHTTPProxy: reflect.TypeOf(HTTPProxyPluginOptions{}),
|
||||
PluginHTTPS2HTTP: reflect.TypeOf(HTTPS2HTTPPluginOptions{}),
|
||||
PluginHTTPS2HTTPS: reflect.TypeOf(HTTPS2HTTPSPluginOptions{}),
|
||||
PluginHTTP2HTTP: reflect.TypeOf(HTTP2HTTPPluginOptions{}),
|
||||
PluginSocks5: reflect.TypeOf(Socks5PluginOptions{}),
|
||||
PluginStaticFile: reflect.TypeOf(StaticFilePluginOptions{}),
|
||||
PluginUnixDomainSocket: reflect.TypeOf(UnixDomainSocketPluginOptions{}),
|
||||
@@ -90,36 +103,61 @@ type HTTP2HTTPSPluginOptions struct {
|
||||
RequestHeaders HeaderOperations `json:"requestHeaders,omitempty"`
|
||||
}
|
||||
|
||||
func (o *HTTP2HTTPSPluginOptions) Complete() {}
|
||||
|
||||
type HTTPProxyPluginOptions struct {
|
||||
Type string `json:"type,omitempty"`
|
||||
HTTPUser string `json:"httpUser,omitempty"`
|
||||
HTTPPassword string `json:"httpPassword,omitempty"`
|
||||
}
|
||||
|
||||
func (o *HTTPProxyPluginOptions) Complete() {}
|
||||
|
||||
type HTTPS2HTTPPluginOptions struct {
|
||||
Type string `json:"type,omitempty"`
|
||||
LocalAddr string `json:"localAddr,omitempty"`
|
||||
HostHeaderRewrite string `json:"hostHeaderRewrite,omitempty"`
|
||||
RequestHeaders HeaderOperations `json:"requestHeaders,omitempty"`
|
||||
EnableHTTP2 *bool `json:"enableHTTP2,omitempty"`
|
||||
CrtPath string `json:"crtPath,omitempty"`
|
||||
KeyPath string `json:"keyPath,omitempty"`
|
||||
}
|
||||
|
||||
func (o *HTTPS2HTTPPluginOptions) Complete() {
|
||||
o.EnableHTTP2 = util.EmptyOr(o.EnableHTTP2, lo.ToPtr(true))
|
||||
}
|
||||
|
||||
type HTTPS2HTTPSPluginOptions struct {
|
||||
Type string `json:"type,omitempty"`
|
||||
LocalAddr string `json:"localAddr,omitempty"`
|
||||
HostHeaderRewrite string `json:"hostHeaderRewrite,omitempty"`
|
||||
RequestHeaders HeaderOperations `json:"requestHeaders,omitempty"`
|
||||
EnableHTTP2 *bool `json:"enableHTTP2,omitempty"`
|
||||
CrtPath string `json:"crtPath,omitempty"`
|
||||
KeyPath string `json:"keyPath,omitempty"`
|
||||
}
|
||||
|
||||
func (o *HTTPS2HTTPSPluginOptions) Complete() {
|
||||
o.EnableHTTP2 = util.EmptyOr(o.EnableHTTP2, lo.ToPtr(true))
|
||||
}
|
||||
|
||||
type HTTP2HTTPPluginOptions struct {
|
||||
Type string `json:"type,omitempty"`
|
||||
LocalAddr string `json:"localAddr,omitempty"`
|
||||
HostHeaderRewrite string `json:"hostHeaderRewrite,omitempty"`
|
||||
RequestHeaders HeaderOperations `json:"requestHeaders,omitempty"`
|
||||
}
|
||||
|
||||
func (o *HTTP2HTTPPluginOptions) Complete() {}
|
||||
|
||||
type Socks5PluginOptions struct {
|
||||
Type string `json:"type,omitempty"`
|
||||
Username string `json:"username,omitempty"`
|
||||
Password string `json:"password,omitempty"`
|
||||
}
|
||||
|
||||
func (o *Socks5PluginOptions) Complete() {}
|
||||
|
||||
type StaticFilePluginOptions struct {
|
||||
Type string `json:"type,omitempty"`
|
||||
LocalPath string `json:"localPath,omitempty"`
|
||||
@@ -128,7 +166,11 @@ type StaticFilePluginOptions struct {
|
||||
HTTPPassword string `json:"httpPassword,omitempty"`
|
||||
}
|
||||
|
||||
func (o *StaticFilePluginOptions) Complete() {}
|
||||
|
||||
type UnixDomainSocketPluginOptions struct {
|
||||
Type string `json:"type,omitempty"`
|
||||
UnixPath string `json:"unixPath,omitempty"`
|
||||
}
|
||||
|
||||
func (o *UnixDomainSocketPluginOptions) Complete() {}
|
||||
|
@@ -127,6 +127,10 @@ func (c *ProxyBaseConfig) Complete(namePrefix string) {
|
||||
c.Name = lo.Ternary(namePrefix == "", "", namePrefix+".") + c.Name
|
||||
c.LocalIP = util.EmptyOr(c.LocalIP, "127.0.0.1")
|
||||
c.Transport.BandwidthLimitMode = util.EmptyOr(c.Transport.BandwidthLimitMode, types.BandwidthLimitModeClient)
|
||||
|
||||
if c.Plugin.ClientPluginOptions != nil {
|
||||
c.Plugin.ClientPluginOptions.Complete()
|
||||
}
|
||||
}
|
||||
|
||||
func (c *ProxyBaseConfig) MarshalToMsg(m *msg.NewProxy) {
|
||||
@@ -195,6 +199,10 @@ func (c *TypedProxyConfig) UnmarshalJSON(b []byte) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *TypedProxyConfig) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal(c.ProxyConfigurer)
|
||||
}
|
||||
|
||||
type ProxyConfigurer interface {
|
||||
Complete(namePrefix string)
|
||||
GetBaseConfig() *ProxyBaseConfig
|
||||
@@ -291,6 +299,7 @@ type HTTPProxyConfig struct {
|
||||
HTTPPassword string `json:"httpPassword,omitempty"`
|
||||
HostHeaderRewrite string `json:"hostHeaderRewrite,omitempty"`
|
||||
RequestHeaders HeaderOperations `json:"requestHeaders,omitempty"`
|
||||
ResponseHeaders HeaderOperations `json:"responseHeaders,omitempty"`
|
||||
RouteByHTTPUser string `json:"routeByHTTPUser,omitempty"`
|
||||
}
|
||||
|
||||
@@ -304,6 +313,7 @@ func (c *HTTPProxyConfig) MarshalToMsg(m *msg.NewProxy) {
|
||||
m.HTTPUser = c.HTTPUser
|
||||
m.HTTPPwd = c.HTTPPassword
|
||||
m.Headers = c.RequestHeaders.Set
|
||||
m.ResponseHeaders = c.ResponseHeaders.Set
|
||||
m.RouteByHTTPUser = c.RouteByHTTPUser
|
||||
}
|
||||
|
||||
@@ -317,6 +327,7 @@ func (c *HTTPProxyConfig) UnmarshalFromMsg(m *msg.NewProxy) {
|
||||
c.HTTPUser = m.HTTPUser
|
||||
c.HTTPPassword = m.HTTPPwd
|
||||
c.RequestHeaders.Set = m.Headers
|
||||
c.ResponseHeaders.Set = m.ResponseHeaders
|
||||
c.RouteByHTTPUser = m.RouteByHTTPUser
|
||||
}
|
||||
|
||||
|
@@ -176,10 +176,15 @@ type ServerTransportConfig struct {
|
||||
|
||||
func (c *ServerTransportConfig) Complete() {
|
||||
c.TCPMux = util.EmptyOr(c.TCPMux, lo.ToPtr(true))
|
||||
c.TCPMuxKeepaliveInterval = util.EmptyOr(c.TCPMuxKeepaliveInterval, 60)
|
||||
c.TCPMuxKeepaliveInterval = util.EmptyOr(c.TCPMuxKeepaliveInterval, 30)
|
||||
c.TCPKeepAlive = util.EmptyOr(c.TCPKeepAlive, 7200)
|
||||
c.MaxPoolCount = util.EmptyOr(c.MaxPoolCount, 5)
|
||||
c.HeartbeatTimeout = util.EmptyOr(c.HeartbeatTimeout, 90)
|
||||
if lo.FromPtr(c.TCPMux) {
|
||||
// If TCPMux is enabled, heartbeat of application layer is unnecessary because we can rely on heartbeat in tcpmux.
|
||||
c.HeartbeatTimeout = util.EmptyOr(c.HeartbeatTimeout, -1)
|
||||
} else {
|
||||
c.HeartbeatTimeout = util.EmptyOr(c.HeartbeatTimeout, 90)
|
||||
}
|
||||
c.QUIC.Complete()
|
||||
if c.TLS.TrustedCaFile != "" {
|
||||
c.TLS.Force = true
|
||||
|
@@ -120,6 +120,10 @@ func (c *TypedVisitorConfig) UnmarshalJSON(b []byte) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *TypedVisitorConfig) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal(c.VisitorConfigurer)
|
||||
}
|
||||
|
||||
func NewVisitorConfigurerByType(t VisitorType) VisitorConfigurer {
|
||||
v, ok := visitorConfigTypeMap[t]
|
||||
if !ok {
|
||||
|
@@ -121,6 +121,7 @@ type NewProxy struct {
|
||||
HTTPPwd string `json:"http_pwd,omitempty"`
|
||||
HostHeaderRewrite string `json:"host_header_rewrite,omitempty"`
|
||||
Headers map[string]string `json:"headers,omitempty"`
|
||||
ResponseHeaders map[string]string `json:"response_headers,omitempty"`
|
||||
RouteByHTTPUser string `json:"route_by_http_user,omitempty"`
|
||||
|
||||
// stcp, sudp, xtcp
|
||||
|
91
pkg/plugin/client/http2http.go
Normal file
@@ -0,0 +1,91 @@
|
||||
// Copyright 2024 The frp Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package plugin
|
||||
|
||||
import (
|
||||
"io"
|
||||
stdlog "log"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/http/httputil"
|
||||
|
||||
"github.com/fatedier/golib/pool"
|
||||
|
||||
v1 "github.com/fatedier/frp/pkg/config/v1"
|
||||
"github.com/fatedier/frp/pkg/util/log"
|
||||
netpkg "github.com/fatedier/frp/pkg/util/net"
|
||||
)
|
||||
|
||||
func init() {
|
||||
Register(v1.PluginHTTP2HTTP, NewHTTP2HTTPPlugin)
|
||||
}
|
||||
|
||||
type HTTP2HTTPPlugin struct {
|
||||
opts *v1.HTTP2HTTPPluginOptions
|
||||
|
||||
l *Listener
|
||||
s *http.Server
|
||||
}
|
||||
|
||||
func NewHTTP2HTTPPlugin(options v1.ClientPluginOptions) (Plugin, error) {
|
||||
opts := options.(*v1.HTTP2HTTPPluginOptions)
|
||||
|
||||
listener := NewProxyListener()
|
||||
|
||||
p := &HTTP2HTTPPlugin{
|
||||
opts: opts,
|
||||
l: listener,
|
||||
}
|
||||
|
||||
rp := &httputil.ReverseProxy{
|
||||
Rewrite: func(r *httputil.ProxyRequest) {
|
||||
req := r.Out
|
||||
req.URL.Scheme = "http"
|
||||
req.URL.Host = p.opts.LocalAddr
|
||||
if p.opts.HostHeaderRewrite != "" {
|
||||
req.Host = p.opts.HostHeaderRewrite
|
||||
}
|
||||
for k, v := range p.opts.RequestHeaders.Set {
|
||||
req.Header.Set(k, v)
|
||||
}
|
||||
},
|
||||
BufferPool: pool.NewBuffer(32 * 1024),
|
||||
ErrorLog: stdlog.New(log.NewWriteLogger(log.WarnLevel, 2), "", 0),
|
||||
}
|
||||
|
||||
p.s = &http.Server{
|
||||
Handler: rp,
|
||||
ReadHeaderTimeout: 0,
|
||||
}
|
||||
|
||||
go func() {
|
||||
_ = p.s.Serve(listener)
|
||||
}()
|
||||
|
||||
return p, nil
|
||||
}
|
||||
|
||||
func (p *HTTP2HTTPPlugin) Handle(conn io.ReadWriteCloser, realConn net.Conn, _ *ExtraInfo) {
|
||||
wrapConn := netpkg.WrapReadWriteCloserToConn(conn, realConn)
|
||||
_ = p.l.PutConn(wrapConn)
|
||||
}
|
||||
|
||||
func (p *HTTP2HTTPPlugin) Name() string {
|
||||
return v1.PluginHTTP2HTTP
|
||||
}
|
||||
|
||||
func (p *HTTP2HTTPPlugin) Close() error {
|
||||
return p.s.Close()
|
||||
}
|
@@ -27,9 +27,11 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/fatedier/golib/pool"
|
||||
"github.com/samber/lo"
|
||||
|
||||
v1 "github.com/fatedier/frp/pkg/config/v1"
|
||||
"github.com/fatedier/frp/pkg/transport"
|
||||
httppkg "github.com/fatedier/frp/pkg/util/http"
|
||||
"github.com/fatedier/frp/pkg/util/log"
|
||||
netpkg "github.com/fatedier/frp/pkg/util/net"
|
||||
)
|
||||
@@ -71,11 +73,17 @@ func NewHTTPS2HTTPPlugin(options v1.ClientPluginOptions) (Plugin, error) {
|
||||
BufferPool: pool.NewBuffer(32 * 1024),
|
||||
ErrorLog: stdlog.New(log.NewWriteLogger(log.WarnLevel, 2), "", 0),
|
||||
}
|
||||
|
||||
p.s = &http.Server{
|
||||
Handler: rp,
|
||||
ReadHeaderTimeout: 60 * time.Second,
|
||||
}
|
||||
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.TLS != nil {
|
||||
tlsServerName, _ := httppkg.CanonicalHost(r.TLS.ServerName)
|
||||
host, _ := httppkg.CanonicalHost(r.Host)
|
||||
if tlsServerName != "" && tlsServerName != host {
|
||||
w.WriteHeader(http.StatusMisdirectedRequest)
|
||||
return
|
||||
}
|
||||
}
|
||||
rp.ServeHTTP(w, r)
|
||||
})
|
||||
|
||||
var (
|
||||
tlsConfig *tls.Config
|
||||
@@ -90,10 +98,18 @@ func NewHTTPS2HTTPPlugin(options v1.ClientPluginOptions) (Plugin, error) {
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("gen TLS config error: %v", err)
|
||||
}
|
||||
ln := tls.NewListener(listener, tlsConfig)
|
||||
|
||||
p.s = &http.Server{
|
||||
Handler: handler,
|
||||
ReadHeaderTimeout: 60 * time.Second,
|
||||
TLSConfig: tlsConfig,
|
||||
}
|
||||
if !lo.FromPtr(opts.EnableHTTP2) {
|
||||
p.s.TLSNextProto = make(map[string]func(*http.Server, *tls.Conn, http.Handler))
|
||||
}
|
||||
|
||||
go func() {
|
||||
_ = p.s.Serve(ln)
|
||||
_ = p.s.ServeTLS(listener, "", "")
|
||||
}()
|
||||
return p, nil
|
||||
}
|
||||
|
@@ -27,9 +27,11 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/fatedier/golib/pool"
|
||||
"github.com/samber/lo"
|
||||
|
||||
v1 "github.com/fatedier/frp/pkg/config/v1"
|
||||
"github.com/fatedier/frp/pkg/transport"
|
||||
httppkg "github.com/fatedier/frp/pkg/util/http"
|
||||
"github.com/fatedier/frp/pkg/util/log"
|
||||
netpkg "github.com/fatedier/frp/pkg/util/net"
|
||||
)
|
||||
@@ -77,11 +79,17 @@ func NewHTTPS2HTTPSPlugin(options v1.ClientPluginOptions) (Plugin, error) {
|
||||
BufferPool: pool.NewBuffer(32 * 1024),
|
||||
ErrorLog: stdlog.New(log.NewWriteLogger(log.WarnLevel, 2), "", 0),
|
||||
}
|
||||
|
||||
p.s = &http.Server{
|
||||
Handler: rp,
|
||||
ReadHeaderTimeout: 60 * time.Second,
|
||||
}
|
||||
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.TLS != nil {
|
||||
tlsServerName, _ := httppkg.CanonicalHost(r.TLS.ServerName)
|
||||
host, _ := httppkg.CanonicalHost(r.Host)
|
||||
if tlsServerName != "" && tlsServerName != host {
|
||||
w.WriteHeader(http.StatusMisdirectedRequest)
|
||||
return
|
||||
}
|
||||
}
|
||||
rp.ServeHTTP(w, r)
|
||||
})
|
||||
|
||||
var (
|
||||
tlsConfig *tls.Config
|
||||
@@ -96,10 +104,18 @@ func NewHTTPS2HTTPSPlugin(options v1.ClientPluginOptions) (Plugin, error) {
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("gen TLS config error: %v", err)
|
||||
}
|
||||
ln := tls.NewListener(listener, tlsConfig)
|
||||
|
||||
p.s = &http.Server{
|
||||
Handler: handler,
|
||||
ReadHeaderTimeout: 60 * time.Second,
|
||||
TLSConfig: tlsConfig,
|
||||
}
|
||||
if !lo.FromPtr(opts.EnableHTTP2) {
|
||||
p.s.TLSNextProto = make(map[string]func(*http.Server, *tls.Conn, http.Handler))
|
||||
}
|
||||
|
||||
go func() {
|
||||
_ = p.s.Serve(ln)
|
||||
_ = p.s.ServeTLS(listener, "", "")
|
||||
}()
|
||||
return p, nil
|
||||
}
|
||||
|
@@ -59,8 +59,12 @@ func fixDNSResolver() {
|
||||
// Note: If there are other methods to obtain the default DNS servers, the default DNS servers should be used preferentially.
|
||||
net.DefaultResolver = &net.Resolver{
|
||||
PreferGo: true,
|
||||
Dial: func(ctx context.Context, network, _ string) (net.Conn, error) {
|
||||
return net.Dial(network, "8.8.8.8:53")
|
||||
Dial: func(ctx context.Context, network, addr string) (net.Conn, error) {
|
||||
if addr == "127.0.0.1:53" || addr == "[::1]:53" {
|
||||
addr = "8.8.8.8:53"
|
||||
}
|
||||
var d net.Dialer
|
||||
return d.DialContext(ctx, network, addr)
|
||||
},
|
||||
}
|
||||
}
|
||||
|
@@ -14,7 +14,7 @@
|
||||
|
||||
package version
|
||||
|
||||
var version = "0.57.0"
|
||||
var version = "0.59.0"
|
||||
|
||||
func Full() string {
|
||||
return version
|
||||
|
@@ -63,9 +63,9 @@ func NewHTTPReverseProxy(option HTTPReverseProxyOptions, vhostRouter *Routers) *
|
||||
req := r.Out
|
||||
req.URL.Scheme = "http"
|
||||
reqRouteInfo := req.Context().Value(RouteInfoKey).(*RequestRouteInfo)
|
||||
oldHost, _ := httppkg.CanonicalHost(reqRouteInfo.Host)
|
||||
originalHost, _ := httppkg.CanonicalHost(reqRouteInfo.Host)
|
||||
|
||||
rc := rp.GetRouteConfig(oldHost, reqRouteInfo.URL, reqRouteInfo.HTTPUser)
|
||||
rc := req.Context().Value(RouteConfigKey).(*RouteConfig)
|
||||
if rc != nil {
|
||||
if rc.RewriteHost != "" {
|
||||
req.Host = rc.RewriteHost
|
||||
@@ -77,7 +77,7 @@ func NewHTTPReverseProxy(option HTTPReverseProxyOptions, vhostRouter *Routers) *
|
||||
endpoint, _ = rc.ChooseEndpointFn()
|
||||
reqRouteInfo.Endpoint = endpoint
|
||||
log.Tracef("choose endpoint name [%s] for http request host [%s] path [%s] httpuser [%s]",
|
||||
endpoint, oldHost, reqRouteInfo.URL, reqRouteInfo.HTTPUser)
|
||||
endpoint, originalHost, reqRouteInfo.URL, reqRouteInfo.HTTPUser)
|
||||
}
|
||||
// Set {domain}.{location}.{routeByHTTPUser}.{endpoint} as URL host here to let http transport reuse connections.
|
||||
req.URL.Host = rc.Domain + "." +
|
||||
@@ -92,6 +92,15 @@ func NewHTTPReverseProxy(option HTTPReverseProxyOptions, vhostRouter *Routers) *
|
||||
req.URL.Host = req.Host
|
||||
}
|
||||
},
|
||||
ModifyResponse: func(r *http.Response) error {
|
||||
rc := r.Request.Context().Value(RouteConfigKey).(*RouteConfig)
|
||||
if rc != nil {
|
||||
for k, v := range rc.ResponseHeaders {
|
||||
r.Header.Set(k, v)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
},
|
||||
// Create a connection to one proxy routed by route policy.
|
||||
Transport: &http.Transport{
|
||||
ResponseHeaderTimeout: rp.responseHeaderTimeout,
|
||||
@@ -157,14 +166,6 @@ func (rp *HTTPReverseProxy) GetRouteConfig(domain, location, routeByHTTPUser str
|
||||
return nil
|
||||
}
|
||||
|
||||
func (rp *HTTPReverseProxy) GetHeaders(domain, location, routeByHTTPUser string) (headers map[string]string) {
|
||||
vr, ok := rp.getVhost(domain, location, routeByHTTPUser)
|
||||
if ok {
|
||||
headers = vr.payload.(*RouteConfig).Headers
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// CreateConnection create a new connection by route config
|
||||
func (rp *HTTPReverseProxy) CreateConnection(reqRouteInfo *RequestRouteInfo, byEndpoint bool) (net.Conn, error) {
|
||||
host, _ := httppkg.CanonicalHost(reqRouteInfo.Host)
|
||||
@@ -305,8 +306,13 @@ func (rp *HTTPReverseProxy) injectRequestInfoToCtx(req *http.Request) *http.Requ
|
||||
RemoteAddr: req.RemoteAddr,
|
||||
URLHost: req.URL.Host,
|
||||
}
|
||||
|
||||
originalHost, _ := httppkg.CanonicalHost(reqRouteInfo.Host)
|
||||
rc := rp.GetRouteConfig(originalHost, reqRouteInfo.URL, reqRouteInfo.HTTPUser)
|
||||
|
||||
newctx := req.Context()
|
||||
newctx = context.WithValue(newctx, RouteInfoKey, reqRouteInfo)
|
||||
newctx = context.WithValue(newctx, RouteConfigKey, rc)
|
||||
return req.Clone(newctx)
|
||||
}
|
||||
|
||||
|
@@ -29,7 +29,8 @@ import (
|
||||
type RouteInfo string
|
||||
|
||||
const (
|
||||
RouteInfoKey RouteInfo = "routeInfo"
|
||||
RouteInfoKey RouteInfo = "routeInfo"
|
||||
RouteConfigKey RouteInfo = "routeConfig"
|
||||
)
|
||||
|
||||
type RequestRouteInfo struct {
|
||||
@@ -113,6 +114,7 @@ type RouteConfig struct {
|
||||
Username string
|
||||
Password string
|
||||
Headers map[string]string
|
||||
ResponseHeaders map[string]string
|
||||
RouteByHTTPUser string
|
||||
|
||||
CreateConnFn CreateConnFunc
|
||||
|
@@ -297,20 +297,18 @@ func (ctl *Control) GetWorkConn() (workConn net.Conn, err error) {
|
||||
}
|
||||
|
||||
func (ctl *Control) heartbeatWorker() {
|
||||
xl := ctl.xl
|
||||
|
||||
// Don't need application heartbeat if TCPMux is enabled,
|
||||
// yamux will do same thing.
|
||||
// TODO(fatedier): let default HeartbeatTimeout to -1 if TCPMux is enabled. Users can still set it to positive value to enable it.
|
||||
if !lo.FromPtr(ctl.serverCfg.Transport.TCPMux) && ctl.serverCfg.Transport.HeartbeatTimeout > 0 {
|
||||
go wait.Until(func() {
|
||||
if time.Since(ctl.lastPing.Load().(time.Time)) > time.Duration(ctl.serverCfg.Transport.HeartbeatTimeout)*time.Second {
|
||||
xl.Warnf("heartbeat timeout")
|
||||
ctl.conn.Close()
|
||||
return
|
||||
}
|
||||
}, time.Second, ctl.doneCh)
|
||||
if ctl.serverCfg.Transport.HeartbeatTimeout <= 0 {
|
||||
return
|
||||
}
|
||||
|
||||
xl := ctl.xl
|
||||
go wait.Until(func() {
|
||||
if time.Since(ctl.lastPing.Load().(time.Time)) > time.Duration(ctl.serverCfg.Transport.HeartbeatTimeout)*time.Second {
|
||||
xl.Warnf("heartbeat timeout")
|
||||
ctl.conn.Close()
|
||||
return
|
||||
}
|
||||
}, time.Second, ctl.doneCh)
|
||||
}
|
||||
|
||||
// block until Control closed
|
||||
|
@@ -58,6 +58,7 @@ func (pxy *HTTPProxy) Run() (remoteAddr string, err error) {
|
||||
RewriteHost: pxy.cfg.HostHeaderRewrite,
|
||||
RouteByHTTPUser: pxy.cfg.RouteByHTTPUser,
|
||||
Headers: pxy.cfg.RequestHeaders.Set,
|
||||
ResponseHeaders: pxy.cfg.ResponseHeaders.Set,
|
||||
Username: pxy.cfg.HTTPUser,
|
||||
Password: pxy.cfg.HTTPPassword,
|
||||
CreateConnFn: pxy.GetRealConn,
|
||||
|
@@ -267,7 +267,7 @@ var _ = ginkgo.Describe("[Feature: HTTP]", func() {
|
||||
Ensure()
|
||||
})
|
||||
|
||||
ginkgo.It("Modify headers", func() {
|
||||
ginkgo.It("Modify request headers", func() {
|
||||
vhostHTTPPort := f.AllocPort()
|
||||
serverConf := getDefaultServerConf(vhostHTTPPort)
|
||||
|
||||
@@ -292,7 +292,6 @@ var _ = ginkgo.Describe("[Feature: HTTP]", func() {
|
||||
|
||||
f.RunProcesses([]string{serverConf}, []string{clientConf})
|
||||
|
||||
// not set auth header
|
||||
framework.NewRequestExpect(f).Port(vhostHTTPPort).
|
||||
RequestModify(func(r *request.Request) {
|
||||
r.HTTP().HTTPHost("normal.example.com")
|
||||
@@ -301,6 +300,40 @@ var _ = ginkgo.Describe("[Feature: HTTP]", func() {
|
||||
Ensure()
|
||||
})
|
||||
|
||||
ginkgo.It("Modify response headers", func() {
|
||||
vhostHTTPPort := f.AllocPort()
|
||||
serverConf := getDefaultServerConf(vhostHTTPPort)
|
||||
|
||||
localPort := f.AllocPort()
|
||||
localServer := httpserver.New(
|
||||
httpserver.WithBindPort(localPort),
|
||||
httpserver.WithHandler(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
||||
w.WriteHeader(200)
|
||||
})),
|
||||
)
|
||||
f.RunServer("", localServer)
|
||||
|
||||
clientConf := consts.DefaultClientConfig
|
||||
clientConf += fmt.Sprintf(`
|
||||
[[proxies]]
|
||||
name = "test"
|
||||
type = "http"
|
||||
localPort = %d
|
||||
customDomains = ["normal.example.com"]
|
||||
responseHeaders.set.x-from-where = "frp"
|
||||
`, localPort)
|
||||
|
||||
f.RunProcesses([]string{serverConf}, []string{clientConf})
|
||||
|
||||
framework.NewRequestExpect(f).Port(vhostHTTPPort).
|
||||
RequestModify(func(r *request.Request) {
|
||||
r.HTTP().HTTPHost("normal.example.com")
|
||||
}).
|
||||
Ensure(func(res *request.Response) bool {
|
||||
return res.Header.Get("X-From-Where") == "frp"
|
||||
})
|
||||
})
|
||||
|
||||
ginkgo.It("Host Header Rewrite", func() {
|
||||
vhostHTTPPort := f.AllocPort()
|
||||
serverConf := getDefaultServerConf(vhostHTTPPort)
|
||||
|
@@ -3,6 +3,7 @@ package plugin
|
||||
import (
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"github.com/onsi/ginkgo/v2"
|
||||
@@ -329,4 +330,76 @@ var _ = ginkgo.Describe("[Feature: Client-Plugins]", func() {
|
||||
ExpectResp([]byte("test")).
|
||||
Ensure()
|
||||
})
|
||||
|
||||
ginkgo.Describe("http2http", func() {
|
||||
ginkgo.It("host header rewrite", func() {
|
||||
serverConf := consts.DefaultServerConfig
|
||||
|
||||
localPort := f.AllocPort()
|
||||
remotePort := f.AllocPort()
|
||||
clientConf := consts.DefaultClientConfig + fmt.Sprintf(`
|
||||
[[proxies]]
|
||||
name = "http2http"
|
||||
type = "tcp"
|
||||
remotePort = %d
|
||||
[proxies.plugin]
|
||||
type = "http2http"
|
||||
localAddr = "127.0.0.1:%d"
|
||||
hostHeaderRewrite = "rewrite.test.com"
|
||||
`, remotePort, localPort)
|
||||
|
||||
f.RunProcesses([]string{serverConf}, []string{clientConf})
|
||||
|
||||
localServer := httpserver.New(
|
||||
httpserver.WithBindPort(localPort),
|
||||
httpserver.WithHandler(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
||||
_, _ = w.Write([]byte(req.Host))
|
||||
})),
|
||||
)
|
||||
f.RunServer("", localServer)
|
||||
|
||||
framework.NewRequestExpect(f).
|
||||
Port(remotePort).
|
||||
RequestModify(func(r *request.Request) {
|
||||
r.HTTP().HTTPHost("example.com")
|
||||
}).
|
||||
ExpectResp([]byte("rewrite.test.com")).
|
||||
Ensure()
|
||||
})
|
||||
|
||||
ginkgo.It("set request header", func() {
|
||||
serverConf := consts.DefaultServerConfig
|
||||
|
||||
localPort := f.AllocPort()
|
||||
remotePort := f.AllocPort()
|
||||
clientConf := consts.DefaultClientConfig + fmt.Sprintf(`
|
||||
[[proxies]]
|
||||
name = "http2http"
|
||||
type = "tcp"
|
||||
remotePort = %d
|
||||
[proxies.plugin]
|
||||
type = "http2http"
|
||||
localAddr = "127.0.0.1:%d"
|
||||
requestHeaders.set.x-from-where = "frp"
|
||||
`, remotePort, localPort)
|
||||
|
||||
f.RunProcesses([]string{serverConf}, []string{clientConf})
|
||||
|
||||
localServer := httpserver.New(
|
||||
httpserver.WithBindPort(localPort),
|
||||
httpserver.WithHandler(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
||||
_, _ = w.Write([]byte(req.Header.Get("x-from-where")))
|
||||
})),
|
||||
)
|
||||
f.RunServer("", localServer)
|
||||
|
||||
framework.NewRequestExpect(f).
|
||||
Port(remotePort).
|
||||
RequestModify(func(r *request.Request) {
|
||||
r.HTTP().HTTPHost("example.com")
|
||||
}).
|
||||
ExpectResp([]byte("frp")).
|
||||
Ensure()
|
||||
})
|
||||
})
|
||||
})
|
||||
|