Feat: 代码提交
This commit is contained in:
@@ -0,0 +1,214 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const (
|
||||
StatusOK = "200 OK"
|
||||
StatusNotFound = "404 Not Found"
|
||||
StatusNotImplemented = "501 Not Implemented"
|
||||
StatusInternalServerError = "500 Internal Server Error"
|
||||
StatusBadRequest = "400 Bad Request"
|
||||
)
|
||||
|
||||
type Response struct {
|
||||
StatusCode string
|
||||
Headers map[string]string
|
||||
Body string
|
||||
ContentType string
|
||||
}
|
||||
|
||||
func getContentType(filePath string) string {
|
||||
ext := strings.ToLower(filepath.Ext(filePath))
|
||||
switch ext {
|
||||
case ".html":
|
||||
return "text/html; charset=utf-8"
|
||||
case ".htm":
|
||||
return "text/html; charset=utf-8"
|
||||
case ".css":
|
||||
return "text/css; charset=utf-8"
|
||||
case ".js":
|
||||
return "application/javascript"
|
||||
case ".json":
|
||||
return "application/json"
|
||||
case ".jpg":
|
||||
return "image/jpeg"
|
||||
case ".jpeg":
|
||||
return "image/jpeg"
|
||||
case ".png":
|
||||
return "image/png"
|
||||
case ".gif":
|
||||
return "image/gif"
|
||||
case ".svg":
|
||||
return "image/svg+xml"
|
||||
case ".ico":
|
||||
return "image/x-icon"
|
||||
case ".txt":
|
||||
return "text/plain; charset=utf-8"
|
||||
case ".pdf":
|
||||
return "application/pdf"
|
||||
default:
|
||||
return "application/octet-stream"
|
||||
}
|
||||
}
|
||||
|
||||
func NewResponse(statusCode, body, contentType string) *Response {
|
||||
return &Response{
|
||||
StatusCode: statusCode,
|
||||
Body: body,
|
||||
ContentType: contentType,
|
||||
Headers: make(map[string]string),
|
||||
}
|
||||
}
|
||||
|
||||
func (r *Response) Build() []byte {
|
||||
var builder strings.Builder
|
||||
|
||||
builder.WriteString(fmt.Sprintf("HTTP/1.1 %s\r\n", r.StatusCode))
|
||||
builder.WriteString(fmt.Sprintf("Content-Type: %s\r\n", r.ContentType))
|
||||
builder.WriteString(fmt.Sprintf("Content-Length: %d\r\n", len(r.Body)))
|
||||
|
||||
for key, value := range r.Headers {
|
||||
builder.WriteString(fmt.Sprintf("%s: %s\r\n", key, value))
|
||||
}
|
||||
|
||||
builder.WriteString("\r\n")
|
||||
builder.WriteString(r.Body)
|
||||
|
||||
return []byte(builder.String())
|
||||
}
|
||||
|
||||
func ReadFile(rootDir, path string) ([]byte, string, error) {
|
||||
filePath := filepath.Join(rootDir, path)
|
||||
|
||||
filePath = filepath.Clean(filePath)
|
||||
|
||||
if strings.Contains(filePath, "..") {
|
||||
return nil, "", fmt.Errorf("invalid path")
|
||||
}
|
||||
|
||||
data, err := os.ReadFile(filePath)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
|
||||
contentType := getContentType(filePath)
|
||||
return data, contentType, nil
|
||||
}
|
||||
|
||||
func BuildOKResponse(body []byte, contentType string) *Response {
|
||||
return &Response{
|
||||
StatusCode: StatusOK,
|
||||
Body: string(body),
|
||||
ContentType: contentType,
|
||||
Headers: make(map[string]string),
|
||||
}
|
||||
}
|
||||
|
||||
func BuildErrorResponse(statusCode, htmlPath, rootDir string) *Response {
|
||||
body := getErrorHTML(statusCode)
|
||||
|
||||
if htmlPath != "" && rootDir != "" {
|
||||
data, contentType, err := ReadFile(rootDir, htmlPath)
|
||||
if err == nil {
|
||||
return &Response{
|
||||
StatusCode: statusCode,
|
||||
Body: string(data),
|
||||
ContentType: contentType,
|
||||
Headers: make(map[string]string),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return &Response{
|
||||
StatusCode: statusCode,
|
||||
Body: body,
|
||||
ContentType: "text/html; charset=utf-8",
|
||||
Headers: make(map[string]string),
|
||||
}
|
||||
}
|
||||
|
||||
func getErrorHTML(statusCode string) string {
|
||||
statusNum := strings.Split(statusCode, " ")[0]
|
||||
var title, message string
|
||||
|
||||
switch statusCode {
|
||||
case StatusNotFound:
|
||||
title = "404 Not Found"
|
||||
message = "The requested resource could not be found."
|
||||
case StatusNotImplemented:
|
||||
title = "501 Not Implemented"
|
||||
message = "The requested method is not supported by this server. Only GET method is supported."
|
||||
case StatusInternalServerError:
|
||||
title = "500 Internal Server Error"
|
||||
message = "An internal server error occurred."
|
||||
case StatusBadRequest:
|
||||
title = "400 Bad Request"
|
||||
message = "The request could not be understood by the server."
|
||||
default:
|
||||
title = statusCode
|
||||
message = "An error occurred while processing your request."
|
||||
}
|
||||
|
||||
html := fmt.Sprintf(`<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>%s</title>
|
||||
<meta charset="utf-8">
|
||||
<style>
|
||||
body {
|
||||
font-family: Arial, sans-serif;
|
||||
text-align: center;
|
||||
background-color: #f0f0f0;
|
||||
margin: 0;
|
||||
padding: 50px;
|
||||
}
|
||||
.error-container {
|
||||
background-color: white;
|
||||
border-radius: 10px;
|
||||
padding: 40px;
|
||||
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
|
||||
max-width: 500px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
h1 {
|
||||
color: #e74c3c;
|
||||
font-size: 72px;
|
||||
margin: 0 0 20px 0;
|
||||
}
|
||||
p {
|
||||
color: #333;
|
||||
font-size: 18px;
|
||||
margin: 0 0 20px 0;
|
||||
}
|
||||
.back-link {
|
||||
color: #3498db;
|
||||
text-decoration: none;
|
||||
}
|
||||
.back-link:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="error-container">
|
||||
<h1>%s</h1>
|
||||
<p>%s</p>
|
||||
<a href="/" class="back-link">返回首页</a>
|
||||
</div>
|
||||
</body>
|
||||
</html>`, title, statusNum, message)
|
||||
|
||||
return html
|
||||
}
|
||||
|
||||
func SendResponse(conn io.Writer, response *Response) error {
|
||||
data := response.Build()
|
||||
_, err := conn.Write(data)
|
||||
return err
|
||||
}
|
||||
Reference in New Issue
Block a user