Browse Source

feat: 网站支持启动 http3 (#6501)

Refs https://github.com/1Panel-dev/1Panel/issues/3641
pull/6538/head
zhengkunwang 2 months ago committed by GitHub
parent
commit
8a5cb6c946
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 1
      agent/app/dto/request/website.go
  2. 1
      agent/app/dto/response/website.go
  3. 54
      agent/app/service/website.go
  4. 78
      agent/app/service/website_utils.go
  5. 26
      agent/utils/nginx/components/server.go
  6. 2
      frontend/src/api/interface/website.ts
  7. 2
      frontend/src/lang/modules/en.ts
  8. 2
      frontend/src/lang/modules/tw.ts
  9. 2
      frontend/src/lang/modules/zh.ts
  10. 12
      frontend/src/views/website/website/config/basic/https/index.vue

1
agent/app/dto/request/website.go

@ -162,6 +162,7 @@ type WebsiteHTTPSOp struct {
Algorithm string `json:"algorithm"`
Hsts bool `json:"hsts"`
HttpsPorts []int `json:"httpsPorts"`
Http3 bool `json:"http3"`
}
type WebsiteNginxUpdate struct {

1
agent/app/dto/response/website.go

@ -64,6 +64,7 @@ type WebsiteHTTPS struct {
Hsts bool `json:"hsts"`
HttpsPorts []int `json:"httpsPorts"`
HttpsPort string `json:"httpsPort"`
Http3 bool `json:"http3"`
}
type WebsiteLog struct {

54
agent/app/service/website.go

@ -620,28 +620,24 @@ func (w WebsiteService) UpdateWebsiteDomain(req request.WebsiteDomainUpdate) err
if err != nil {
return err
}
if website.Protocol == constant.ProtocolHTTPS {
nginxFull, err := getNginxFull(&website)
if err != nil {
return nil
}
nginxConfig := nginxFull.SiteConfig
config := nginxFull.SiteConfig.Config
server := config.FindServers()[0]
var params []string
if domain.SSL {
params = append(params, "ssl", "http2")
}
server.UpdateListen(strconv.Itoa(domain.Port), false, params...)
if website.IPV6 {
server.UpdateListen("[::]:"+strconv.Itoa(domain.Port), false, params...)
}
if err = nginx.WriteConfig(config, nginx.IndentedStyle); err != nil {
return err
}
if err = nginxCheckAndReload(nginxConfig.OldContent, nginxConfig.FilePath, nginxFull.Install.ContainerName); err != nil {
return err
}
nginxFull, err := getNginxFull(&website)
if err != nil {
return nil
}
nginxConfig := nginxFull.SiteConfig
config := nginxFull.SiteConfig.Config
server := config.FindServers()[0]
server.DeleteListen(strconv.Itoa(domain.Port))
if website.IPV6 {
server.DeleteListen("[::]:" + strconv.Itoa(domain.Port))
}
http3 := isHttp3(server)
setListen(server, strconv.Itoa(domain.Port), website.IPV6, http3, website.DefaultServer, domain.SSL && website.Protocol == constant.ProtocolHTTPS)
if err = nginx.WriteConfig(config, nginx.IndentedStyle); err != nil {
return err
}
if err = nginxCheckAndReload(nginxConfig.OldContent, nginxConfig.FilePath, nginxFull.Install.ContainerName); err != nil {
return err
}
return websiteDomainRepo.Save(context.TODO(), &domain)
}
@ -921,7 +917,7 @@ func (w WebsiteService) GetWebsiteHTTPS(websiteId uint) (response.WebsiteHTTPS,
} else {
res.HttpConfig = constant.HTTPToHTTPS
}
params, err := getNginxParamsByKeys(constant.NginxScopeServer, []string{"ssl_protocols", "ssl_ciphers", "add_header"}, &website)
params, err := getNginxParamsByKeys(constant.NginxScopeServer, []string{"ssl_protocols", "ssl_ciphers", "add_header", "listen"}, &website)
if err != nil {
return res, err
}
@ -932,8 +928,13 @@ func (w WebsiteService) GetWebsiteHTTPS(websiteId uint) (response.WebsiteHTTPS,
if p.Name == "ssl_ciphers" {
res.Algorithm = p.Params[0]
}
if p.Name == "add_header" && len(p.Params) > 0 && p.Params[0] == "Strict-Transport-Security" {
res.Hsts = true
if p.Name == "add_header" && len(p.Params) > 0 {
if p.Params[0] == "Strict-Transport-Security" {
res.Hsts = true
}
if p.Params[0] == "Alt-Svc" {
res.Http3 = true
}
}
}
return res, nil
@ -998,6 +999,9 @@ func (w WebsiteService) OpWebsiteHTTPS(ctx context.Context, req request.WebsiteH
dto.NginxParam{
Name: "ssl_ciphers",
},
dto.NginxParam{
Name: "http2",
},
)
if err = deleteNginxConfig(constant.NginxScopeServer, nginxParams, &website); err != nil {
return nil, err

78
agent/app/service/website_utils.go

@ -198,10 +198,7 @@ func configDefaultNginx(website *model.Website, domains []model.WebsiteDomain, a
var serverNames []string
for _, domain := range domains {
serverNames = append(serverNames, domain.Domain)
server.UpdateListen(strconv.Itoa(domain.Port), false)
if website.IPV6 {
server.UpdateListen("[::]:"+strconv.Itoa(domain.Port), false)
}
setListen(server, strconv.Itoa(domain.Port), website.IPV6, false, website.DefaultServer, false)
}
server.UpdateServerName(serverNames)
@ -463,6 +460,17 @@ func delWafConfig(website model.Website, force bool) error {
return nil
}
func isHttp3(server *components.Server) bool {
for _, listen := range server.Listens {
for _, param := range listen.Parameters {
if param == "quic" {
return true
}
}
}
return false
}
func addListenAndServerName(website model.Website, domains []model.WebsiteDomain) error {
nginxFull, err := getNginxFull(&website)
if err != nil {
@ -471,16 +479,10 @@ func addListenAndServerName(website model.Website, domains []model.WebsiteDomain
nginxConfig := nginxFull.SiteConfig
config := nginxFull.SiteConfig.Config
server := config.FindServers()[0]
http3 := isHttp3(server)
for _, domain := range domains {
var params []string
if website.Protocol == constant.ProtocolHTTPS && domain.SSL {
params = append(params, "ssl", "http2")
}
server.UpdateListen(strconv.Itoa(domain.Port), false, params...)
if website.IPV6 {
server.UpdateListen("[::]:"+strconv.Itoa(domain.Port), false, params...)
}
setListen(server, strconv.Itoa(domain.Port), website.IPV6, http3, website.DefaultServer, website.Protocol == constant.ProtocolHTTPS && domain.SSL)
server.UpdateServerName([]string{domain.Domain})
}
@ -512,6 +514,24 @@ func deleteListenAndServerName(website model.Website, binds []string, domains []
return nginxCheckAndReload(nginxConfig.OldContent, nginxConfig.FilePath, nginxFull.Install.ContainerName)
}
func setListen(server *components.Server, port string, ipv6, http3, defaultServer, ssl bool) {
var params []string
if ssl {
params = []string{"ssl"}
}
server.UpdateListen(port, defaultServer, params...)
if ssl && http3 {
server.UpdateListen(port, defaultServer, "quic")
}
if !ipv6 {
return
}
server.UpdateListen("[::]:"+port, defaultServer, params...)
if ssl && http3 {
server.UpdateListen("[::]:"+port, defaultServer, "quic")
}
}
func removeSSLListen(website model.Website, binds []string) error {
nginxFull, err := getNginxFull(&website)
if err != nil {
@ -520,11 +540,13 @@ func removeSSLListen(website model.Website, binds []string) error {
nginxConfig := nginxFull.SiteConfig
config := nginxFull.SiteConfig.Config
server := config.FindServers()[0]
http3 := isHttp3(server)
for _, bind := range binds {
server.UpdateListen(bind, false)
server.DeleteListen(bind)
if website.IPV6 {
server.UpdateListen("[::]:"+bind, false)
server.DeleteListen("[::]:" + bind)
}
setListen(server, bind, website.IPV6, http3, website.DefaultServer, website.Protocol == constant.ProtocolHTTPS)
}
if err := nginx.WriteConfig(config, nginx.IndentedStyle); err != nil {
return err
@ -609,13 +631,11 @@ func applySSL(website *model.Website, websiteSSL model.WebsiteSSL, req request.W
httpPortIPV6 := "[::]:" + httpPort
for _, port := range httpsPort {
httpsPortIPV6 := "[::]:" + strconv.Itoa(port)
server.UpdateListen(strconv.Itoa(port), website.DefaultServer, "ssl", "http2")
if website.IPV6 {
server.UpdateListen(httpsPortIPV6, website.DefaultServer, "ssl", "http2")
}
setListen(server, strconv.Itoa(port), website.IPV6, req.Http3, website.DefaultServer, true)
}
server.UpdateDirective("http2", []string{"on"})
switch req.HttpConfig {
case constant.HTTPSOnly:
server.RemoveListenByBind(httpPort)
@ -642,11 +662,21 @@ func applySSL(website *model.Website, websiteSSL model.WebsiteSSL, req request.W
if !req.Hsts {
server.RemoveDirective("add_header", []string{"Strict-Transport-Security", "\"max-age=31536000\""})
}
if !req.Http3 {
for _, port := range httpsPort {
server.RemoveListen(strconv.Itoa(port), "quic")
if website.IPV6 {
httpsPortIPV6 := "[::]:" + strconv.Itoa(port)
server.RemoveListen(httpsPortIPV6, "quic")
}
}
server.RemoveDirective("add_header", []string{"Alt-Svc"})
}
if err := nginx.WriteConfig(config, nginx.IndentedStyle); err != nil {
if err = nginx.WriteConfig(config, nginx.IndentedStyle); err != nil {
return err
}
if err := createPemFile(*website, websiteSSL); err != nil {
if err = createPemFile(*website, websiteSSL); err != nil {
return err
}
nginxParams := getNginxParamsFromStaticFile(dto.SSL, []dto.NginxParam{})
@ -670,6 +700,12 @@ func applySSL(website *model.Website, websiteSSL model.WebsiteSSL, req request.W
Params: []string{"Strict-Transport-Security", "\"max-age=31536000\""},
})
}
if req.Http3 {
nginxParams = append(nginxParams, dto.NginxParam{
Name: "add_header",
Params: []string{"Alt-Svc", "'h3=\":443\"; ma=2592000'"},
})
}
if err := updateNginxConfig(constant.NginxScopeServer, nginxParams, website); err != nil {
return err

26
agent/utils/nginx/components/server.go

@ -174,6 +174,19 @@ func (s *Server) AddListen(bind string, defaultServer bool, params ...string) {
s.Listens = append(s.Listens, listen)
}
func isSameArray(arr1, arr2 []string) bool {
set1 := make(map[string]struct{})
for _, v := range arr1 {
set1[v] = struct{}{}
}
for _, v := range arr2 {
if _, exists := set1[v]; !exists {
return false
}
}
return true
}
func (s *Server) UpdateListen(bind string, defaultServer bool, params ...string) {
listen := &ServerListen{
Bind: bind,
@ -185,7 +198,7 @@ func (s *Server) UpdateListen(bind string, defaultServer bool, params ...string)
var newListens []*ServerListen
exist := false
for _, li := range s.Listens {
if li.Bind == bind {
if li.Bind == bind && isSameArray(li.Parameters, params) {
exist = true
newListens = append(newListens, listen)
} else {
@ -209,6 +222,17 @@ func (s *Server) DeleteListen(bind string) {
s.Listens = newListens
}
func (s *Server) RemoveListen(bind string, params ...string) {
var newListens []*ServerListen
for _, li := range s.Listens {
if li.Bind == bind && isSameArray(li.Parameters, params) {
continue
}
newListens = append(newListens, li)
}
s.Listens = newListens
}
func (s *Server) DeleteServerName(name string) {
var names []string
dirs := s.FindDirectives("server_name")

2
frontend/src/api/interface/website.ts

@ -296,6 +296,7 @@ export namespace Website {
httpConfig: string;
SSLProtocol: string[];
algorithm: string;
http3: boolean;
}
export interface HTTPSConfig {
@ -306,6 +307,7 @@ export namespace Website {
algorithm: string;
hsts: boolean;
httpsPort?: string;
http3: boolean;
}
export interface CheckReq {

2
frontend/src/lang/modules/en.ts

@ -2202,6 +2202,8 @@ const message = {
ipFromExample2: "If the frontend is a CDN, you can enter the CDN's IP address range",
ipFromExample3:
'If unsure, you can enter 0.0.0.0/0 (ipv4) ::/0 (ipv6) [Note: Allowing any source IP is not secure]',
http3Helper:
'HTTP/3 is an upgrade to HTTP/2, offering faster connection speeds and better performance, but not all browsers support HTTP/3. Enabling it may cause some browsers to be unable to access the site.',
},
php: {
short_open_tag: 'Short tag support',

2
frontend/src/lang/modules/tw.ts

@ -2049,6 +2049,8 @@ const message = {
ipFromExample1: '如果前端是 Frp 等工具可以填寫 Frp IP 地址類似 127.0.0.1',
ipFromExample2: '如果前端是 CDN可以填寫 CDN IP 地址段',
ipFromExample3: '如果不確定可以填 0.0.0.0/0ipv4 ::/0ipv6 [注意允許任意來源 IP 不安全]',
http3Helper:
'HTTP/3 HTTP/2 的升級版本提供更快的連線速度和更好的性能但並非所有瀏覽器都支援 HTTP/3啟用後可能會導致部分瀏覽器無法訪問',
},
php: {
short_open_tag: '短標簽支持',

2
frontend/src/lang/modules/zh.ts

@ -2050,6 +2050,8 @@ const message = {
ipFromExample1: '如果前端是 Frp 等工具可以填写 Frp IP 地址类似 127.0.0.1',
ipFromExample2: '如果前端是 CDN可以填写 CDN IP 地址段',
ipFromExample3: '如果不确定可以填 0.0.0.0/0ipv4 ::/0ipv6 [注意允许任意来源 IP 不安全]',
http3Helper:
'HTTP/3 HTTP/2 的升级版本提供更快的连接速度和更好的性能但是不是所有浏览器都支持 HTTP/3开启后可能会导致部分浏览器无法访问',
},
php: {
short_open_tag: '短标签支持',

12
frontend/src/views/website/website/config/basic/https/index.vue

@ -1,6 +1,6 @@
<template>
<el-row :gutter="20" v-loading="loading">
<el-col :xs="24" :sm="18" :md="18" :lg="18" :xl="14">
<el-col :xs="24" :sm="18" :md="18" :lg="14" :xl="14">
<el-form
class="moblie-form"
ref="httpsForm"
@ -29,8 +29,12 @@
<el-checkbox v-model="form.hsts">{{ $t('commons.button.enable') }}</el-checkbox>
<span class="input-help">{{ $t('website.hstsHelper') }}</span>
</el-form-item>
<el-form-item :label="'HTTP3'" prop="http3">
<el-checkbox v-model="form.http3">{{ $t('commons.button.enable') }}</el-checkbox>
<span class="input-help">{{ $t('website.http3Helper') }}</span>
</el-form-item>
<el-form-item :label="$t('website.sslConfig')" prop="type">
<el-select v-model="form.type" @change="changeType(form.type)">
<el-select v-model="form.type" @change="changeType(form.type)" class="p-w-400">
<el-option :label="$t('website.oldSSL')" :value="'existed'"></el-option>
<el-option :label="$t('website.manualSSL')" :value="'manual'"></el-option>
</el-select>
@ -41,6 +45,7 @@
v-model="form.acmeAccountID"
:placeholder="$t('website.selectAcme')"
@change="listSSL"
class="p-w-400"
>
<el-option :key="0" :label="$t('website.imported')" :value="0"></el-option>
<el-option
@ -61,6 +66,7 @@
v-model="form.websiteSSLId"
:placeholder="$t('website.selectSSL')"
@change="changeSSl(form.websiteSSLId)"
class="p-w-400"
>
<el-option
v-for="(ssl, index) in ssls"
@ -205,6 +211,7 @@ const form = reactive({
'ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA256:!aNULL:!eNULL:!EXPORT:!DSS:!DES:!RC4:!3DES:!MD5:!PSK:!KRB5:!SRP:!CAMELLIA:!SEED',
SSLProtocol: ['TLSv1.3', 'TLSv1.2', 'TLSv1.1', 'TLSv1'],
httpsPort: '443',
http3: false,
});
const loading = ref(false);
const ssls = ref();
@ -300,6 +307,7 @@ const get = () => {
form.acmeAccountID = data.SSL.acmeAccountId;
}
form.hsts = data.hsts;
form.http3 = data.http3;
form.httpsPort = data.httpsPort;
}
listSSL();

Loading…
Cancel
Save