mirror of
https://github.com/netfun2000/rttys_zhaojh329.git
synced 2026-02-27 09:53:24 +08:00
Write the HTTP proxy error page to a file
Signed-off-by: Jianhui Zhao <zhaojh329@gmail.com>
This commit is contained in:
@@ -26,6 +26,7 @@ jobs:
|
|||||||
cd ui
|
cd ui
|
||||||
npm install
|
npm install
|
||||||
npm run build
|
npm run build
|
||||||
|
mv dist ../assets
|
||||||
cd ..
|
cd ..
|
||||||
- name: build
|
- name: build
|
||||||
env:
|
env:
|
||||||
|
|||||||
@@ -82,6 +82,7 @@ jobs:
|
|||||||
cd ui
|
cd ui
|
||||||
npm install
|
npm install
|
||||||
npm run build
|
npm run build
|
||||||
|
mv dist ../assets
|
||||||
cd ..
|
cd ..
|
||||||
|
|
||||||
- name: Build rttys release
|
- name: Build rttys release
|
||||||
|
|||||||
@@ -12,3 +12,5 @@
|
|||||||
|
|
||||||
# Project-local glide cache, RE: https://github.com/Masterminds/glide/issues/736
|
# Project-local glide cache, RE: https://github.com/Masterminds/glide/issues/736
|
||||||
.glide/
|
.glide/
|
||||||
|
|
||||||
|
assets/dist
|
||||||
|
|||||||
+1
-1
@@ -6,7 +6,7 @@ RUN npm install && npm run build
|
|||||||
FROM golang:latest AS rttys
|
FROM golang:latest AS rttys
|
||||||
WORKDIR /rttys-build
|
WORKDIR /rttys-build
|
||||||
COPY . .
|
COPY . .
|
||||||
COPY --from=ui /rttys-ui/dist ui/dist
|
COPY --from=ui /rttys-ui/dist assets/dist
|
||||||
RUN CGO_ENABLED=0 \
|
RUN CGO_ENABLED=0 \
|
||||||
GitCommit=$(git log --pretty=format:"%h" -1) \
|
GitCommit=$(git log --pretty=format:"%h" -1) \
|
||||||
BuildTime=$(date +%FT%T%z) \
|
BuildTime=$(date +%FT%T%z) \
|
||||||
|
|||||||
@@ -6,7 +6,6 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"embed"
|
|
||||||
"io/fs"
|
"io/fs"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
@@ -24,9 +23,6 @@ import (
|
|||||||
|
|
||||||
const httpSessionExpire = 30 * time.Minute
|
const httpSessionExpire = 30 * time.Minute
|
||||||
|
|
||||||
//go:embed ui/dist
|
|
||||||
var staticFs embed.FS
|
|
||||||
|
|
||||||
func (srv *RttyServer) ListenAPI() error {
|
func (srv *RttyServer) ListenAPI() error {
|
||||||
cfg := &srv.cfg
|
cfg := &srv.cfg
|
||||||
|
|
||||||
@@ -34,7 +30,7 @@ func (srv *RttyServer) ListenAPI() error {
|
|||||||
|
|
||||||
r := gin.New()
|
r := gin.New()
|
||||||
|
|
||||||
fs, err := fs.Sub(staticFs, "ui/dist")
|
fs, err := fs.Sub(staticFs, "assets/dist")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,151 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>RTTY</title>
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
|
||||||
|
background-color: #555;
|
||||||
|
line-height: 1.6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.error-container {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
min-height: 60vh;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.error-icon {
|
||||||
|
margin-bottom: 2rem;
|
||||||
|
animation: fadeIn 0.8s ease-in-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
.error-icon svg {
|
||||||
|
width: 90px;
|
||||||
|
height: 90px;
|
||||||
|
fill: #f56565;
|
||||||
|
}
|
||||||
|
|
||||||
|
.error-content {
|
||||||
|
max-width: 700px;
|
||||||
|
animation: slideUp 0.8s ease-out 0.2s both;
|
||||||
|
}
|
||||||
|
|
||||||
|
.error-title {
|
||||||
|
font-size: 1.8rem;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #7a8fb0;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
line-height: 1.2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.error-message {
|
||||||
|
font-size: 1rem;
|
||||||
|
color: #b6c1d3;
|
||||||
|
margin-bottom: 2rem;
|
||||||
|
line-height: 1.6;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes fadeIn {
|
||||||
|
from {
|
||||||
|
opacity: 0;
|
||||||
|
transform: scale(0.8);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
transform: scale(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes slideUp {
|
||||||
|
from {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateY(20px);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="error-container">
|
||||||
|
<div class="error-icon">
|
||||||
|
<svg viewBox="0 0 24 24">
|
||||||
|
<path d="M1 21h22L12 2 1 21zm12-3h-2v-2h2v2zm0-4h-2v-4h2v4z"/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<div class="error-content">
|
||||||
|
<h2 class="error-title" id="errorTitle"></h2>
|
||||||
|
<p class="error-message" id="errorMessage"></p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
const translations = {
|
||||||
|
en: {
|
||||||
|
'Device Unavailable': 'Device Unavailable',
|
||||||
|
'Invalid Request': 'Invalid Request',
|
||||||
|
'Unauthorized Access': 'Unauthorized Access',
|
||||||
|
'Device offline message': 'The device is currently offline. Please check the device status and try again.',
|
||||||
|
'Invalid request message': 'The request is invalid or malformed',
|
||||||
|
'Unauthorized request message': 'You are not authorized to access this resource. Please check your session and try again.'
|
||||||
|
},
|
||||||
|
'zh-CN': {
|
||||||
|
'Device Unavailable': '设备不可用',
|
||||||
|
'Invalid Request': '无效请求',
|
||||||
|
'Unauthorized Access': '未授权访问',
|
||||||
|
'Device offline message': '设备当前离线,请检查设备状态后重试。',
|
||||||
|
'Invalid request message': '请求无效或格式错误',
|
||||||
|
'Unauthorized request message': '您无权访问此资源。请检查您的会话并重试。'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
function t(key, lang) {
|
||||||
|
return translations[lang][key] || translations.en[key] || key;
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateContent() {
|
||||||
|
const errorType = '{{.}}';
|
||||||
|
const lang = navigator.language === 'zh-CN' ? 'zh-CN' : 'en';
|
||||||
|
|
||||||
|
let title = '', message = '';
|
||||||
|
|
||||||
|
switch (errorType) {
|
||||||
|
case 'offline':
|
||||||
|
title = t('Device Unavailable', lang);
|
||||||
|
message = t('Device offline message', lang);
|
||||||
|
break;
|
||||||
|
case 'invalid':
|
||||||
|
title = t('Invalid Request', lang);
|
||||||
|
message = t('Invalid request message', lang);
|
||||||
|
break;
|
||||||
|
case 'unauthorized':
|
||||||
|
title = t('Unauthorized Access', lang);
|
||||||
|
message = t('Unauthorized request message', lang);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
document.getElementById('errorTitle').textContent = title;
|
||||||
|
document.getElementById('errorMessage').textContent = message;
|
||||||
|
|
||||||
|
// Update page title
|
||||||
|
if (title) {
|
||||||
|
document.title = title + ' - RTTY';
|
||||||
|
} else {
|
||||||
|
document.title = 'Error - RTTY';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize page on load
|
||||||
|
document.addEventListener('DOMContentLoaded', updateContent);
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -21,6 +21,11 @@ ARCH="$1" # Pass architecture as an argument, e.g., amd64 or arm64
|
|||||||
exit 1;
|
exit 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[ -d assets/dist ] || {
|
||||||
|
echo "Please build ui first"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
# Build directory
|
# Build directory
|
||||||
BUILD_DIR="build-deb"
|
BUILD_DIR="build-deb"
|
||||||
INSTALL_DIR="$BUILD_DIR/usr"
|
INSTALL_DIR="$BUILD_DIR/usr"
|
||||||
|
|||||||
@@ -10,6 +10,11 @@ BuildTime=$(date +%FT%T%z)
|
|||||||
exit 1
|
exit 1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[ -d assets/dist ] || {
|
||||||
|
echo "Please build ui first"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
generate() {
|
generate() {
|
||||||
local os="$1"
|
local os="$1"
|
||||||
local arch="$2"
|
local arch="$2"
|
||||||
|
|||||||
@@ -0,0 +1,11 @@
|
|||||||
|
/* SPDX-License-Identifier: MIT */
|
||||||
|
/*
|
||||||
|
* Author: Jianhui Zhao <zhaojh329@gmail.com>
|
||||||
|
*/
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import "embed"
|
||||||
|
|
||||||
|
//go:embed assets
|
||||||
|
var staticFs embed.FS
|
||||||
@@ -7,10 +7,13 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"io/fs"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
@@ -373,170 +376,30 @@ func (rw *HttpProxyWriter) WriteRequest(req *http.Request) {
|
|||||||
req.Write(rw)
|
req.Write(rw)
|
||||||
}
|
}
|
||||||
|
|
||||||
func generateErrorHTML(errorType string) string {
|
|
||||||
return fmt.Sprintf(
|
|
||||||
`<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
||||||
<title>RTTY</title>
|
|
||||||
<style>
|
|
||||||
body {
|
|
||||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
|
|
||||||
background-color: #555;
|
|
||||||
line-height: 1.6;
|
|
||||||
}
|
|
||||||
|
|
||||||
.error-container {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
min-height: 60vh;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.error-icon {
|
|
||||||
margin-bottom: 2rem;
|
|
||||||
animation: fadeIn 0.8s ease-in-out;
|
|
||||||
}
|
|
||||||
|
|
||||||
.error-icon svg {
|
|
||||||
width: 90px;
|
|
||||||
height: 90px;
|
|
||||||
fill: #f56565;
|
|
||||||
}
|
|
||||||
|
|
||||||
.error-content {
|
|
||||||
max-width: 700px;
|
|
||||||
animation: slideUp 0.8s ease-out 0.2s both;
|
|
||||||
}
|
|
||||||
|
|
||||||
.error-title {
|
|
||||||
font-size: 1.8rem;
|
|
||||||
font-weight: 600;
|
|
||||||
color: #7a8fb0;
|
|
||||||
margin-bottom: 1rem;
|
|
||||||
line-height: 1.2;
|
|
||||||
}
|
|
||||||
|
|
||||||
.error-message {
|
|
||||||
font-size: 1rem;
|
|
||||||
color: #b6c1d3;
|
|
||||||
margin-bottom: 2rem;
|
|
||||||
line-height: 1.6;
|
|
||||||
text-align: left;
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes fadeIn {
|
|
||||||
from {
|
|
||||||
opacity: 0;
|
|
||||||
transform: scale(0.8);
|
|
||||||
}
|
|
||||||
to {
|
|
||||||
opacity: 1;
|
|
||||||
transform: scale(1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes slideUp {
|
|
||||||
from {
|
|
||||||
opacity: 0;
|
|
||||||
transform: translateY(20px);
|
|
||||||
}
|
|
||||||
to {
|
|
||||||
opacity: 1;
|
|
||||||
transform: translateY(0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div class="error-container">
|
|
||||||
<div class="error-icon">
|
|
||||||
<svg viewBox="0 0 24 24">
|
|
||||||
<path d="M1 21h22L12 2 1 21zm12-3h-2v-2h2v2zm0-4h-2v-4h2v4z"/>
|
|
||||||
</svg>
|
|
||||||
</div>
|
|
||||||
<div class="error-content">
|
|
||||||
<h2 class="error-title" id="errorTitle"></h2>
|
|
||||||
<p class="error-message" id="errorMessage"></p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
const translations = {
|
|
||||||
en: {
|
|
||||||
'Device Unavailable': 'Device Unavailable',
|
|
||||||
'Invalid Request': 'Invalid Request',
|
|
||||||
'Unauthorized Access': 'Unauthorized Access',
|
|
||||||
'Device offline message': 'The device is currently offline. Please check the device status and try again.',
|
|
||||||
'Invalid request message': 'The request is invalid or malformed',
|
|
||||||
'Unauthorized request message': 'You are not authorized to access this resource. Please check your session and try again.'
|
|
||||||
},
|
|
||||||
'zh-CN': {
|
|
||||||
'Device Unavailable': '设备不可用',
|
|
||||||
'Invalid Request': '无效请求',
|
|
||||||
'Unauthorized Access': '未授权访问',
|
|
||||||
'Device offline message': '设备当前离线,请检查设备状态后重试。',
|
|
||||||
'Invalid request message': '请求无效或格式错误',
|
|
||||||
'Unauthorized request message': '您无权访问此资源。请检查您的会话并重试。'
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
function t(key, lang) {
|
|
||||||
return translations[lang][key] || translations.en[key] || key;
|
|
||||||
}
|
|
||||||
|
|
||||||
function updateContent() {
|
|
||||||
const errorType = '%s';
|
|
||||||
const lang = navigator.language === 'zh-CN' ? 'zh-CN' : 'en';
|
|
||||||
|
|
||||||
let title = '', message = '';
|
|
||||||
|
|
||||||
switch (errorType) {
|
|
||||||
case 'offline':
|
|
||||||
title = t('Device Unavailable', lang);
|
|
||||||
message = t('Device offline message', lang);
|
|
||||||
break;
|
|
||||||
case 'invalid':
|
|
||||||
title = t('Invalid Request', lang);
|
|
||||||
message = t('Invalid request message', lang);
|
|
||||||
break;
|
|
||||||
case 'unauthorized':
|
|
||||||
title = t('Unauthorized Access', lang);
|
|
||||||
message = t('Unauthorized request message', lang);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
document.getElementById('errorTitle').textContent = title;
|
|
||||||
document.getElementById('errorMessage').textContent = message;
|
|
||||||
|
|
||||||
// Update page title
|
|
||||||
if (title) {
|
|
||||||
document.title = title + ' - RTTY';
|
|
||||||
} else {
|
|
||||||
document.title = 'Error - RTTY';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Initialize page on load
|
|
||||||
document.addEventListener('DOMContentLoaded', updateContent);
|
|
||||||
</script>
|
|
||||||
</body>
|
|
||||||
</html>`, errorType)
|
|
||||||
}
|
|
||||||
|
|
||||||
func sendHTTPErrorResponse(conn net.Conn, errorType string) {
|
func sendHTTPErrorResponse(conn net.Conn, errorType string) {
|
||||||
htmlContent := generateErrorHTML(errorType)
|
fs, _ := fs.Sub(staticFs, "assets")
|
||||||
|
|
||||||
|
f, err := fs.Open("http-proxy-err.html")
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
|
||||||
|
content, err := io.ReadAll(f)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
content = bytes.ReplaceAll(content, []byte("{{.}}"), []byte(errorType))
|
||||||
|
|
||||||
response := "HTTP/1.1 200 OK\r\n"
|
response := "HTTP/1.1 200 OK\r\n"
|
||||||
response += "Content-Type: text/html; charset=utf-8\r\n"
|
response += "Content-Type: text/html; charset=utf-8\r\n"
|
||||||
response += fmt.Sprintf("Content-Length: %d\r\n", len(htmlContent))
|
response += fmt.Sprintf("Content-Length: %d\r\n", len(content))
|
||||||
response += "Connection: close\r\n"
|
response += "Connection: close\r\n"
|
||||||
response += "\r\n"
|
response += "\r\n"
|
||||||
response += htmlContent
|
|
||||||
|
|
||||||
conn.Write([]byte(response))
|
conn.Write([]byte(response))
|
||||||
|
conn.Write(content)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user