Инициализация проекта

Стабильный рабочий проект.
This commit is contained in:
Falknat
2025-10-02 06:02:45 +07:00
commit 7a87617282
47 changed files with 6057 additions and 0 deletions

View File

@@ -0,0 +1,505 @@
package webserver
import (
"bytes"
"encoding/binary"
"fmt"
"io"
"net"
"net/http"
"os"
"os/exec"
"path/filepath"
"strconv"
"strings"
"sync"
"time"
config "vServer/Backend/config"
tools "vServer/Backend/tools"
)
var (
phpProcesses []*exec.Cmd
fcgiPorts []int
portIndex int
portMutex sync.Mutex
maxWorkers = 4
stopping = false // Флаг остановки
)
var address_php string
var Сonsole_php bool = false
// FastCGI константы
const (
FCGI_VERSION_1 = 1
FCGI_BEGIN_REQUEST = 1
FCGI_ABORT_REQUEST = 2
FCGI_END_REQUEST = 3
FCGI_PARAMS = 4
FCGI_STDIN = 5
FCGI_STDOUT = 6
FCGI_STDERR = 7
FCGI_DATA = 8
FCGI_GET_VALUES = 9
FCGI_GET_VALUES_RESULT = 10
FCGI_UNKNOWN_TYPE = 11
FCGI_MAXTYPE = FCGI_UNKNOWN_TYPE
FCGI_NULL_REQUEST_ID = 0
FCGI_KEEP_CONN = 1
FCGI_RESPONDER = 1
FCGI_AUTHORIZER = 2
FCGI_FILTER = 3
)
// FastCGI заголовок
type FCGIHeader struct {
Version byte
Type byte
RequestID uint16
ContentLength uint16
PaddingLength byte
Reserved byte
}
// FastCGI BeginRequest body
type FCGIBeginRequestBody struct {
Role uint16
Flags byte
Reserved [5]byte
}
func PHP_Start() {
// Сбрасываем флаг остановки
stopping = false
// Читаем настройки из конфига
address_php = config.ConfigData.Soft_Settings.Php_host
// Запускаем FastCGI процессы
for i := 0; i < maxWorkers; i++ {
port := config.ConfigData.Soft_Settings.Php_port + i
fcgiPorts = append(fcgiPorts, port)
go startFastCGIWorker(port, i)
time.Sleep(200 * time.Millisecond) // Задержка между запусками
}
tools.Logs_file(0, "PHP ", fmt.Sprintf("💻 PHP FastCGI пул запущен (%d процессов на портах %d-%d)", maxWorkers, config.ConfigData.Soft_Settings.Php_port, config.ConfigData.Soft_Settings.Php_port+maxWorkers-1), "logs_php.log", true)
}
func startFastCGIWorker(port int, workerID int) {
phpPath := "WebServer/soft/PHP/php_v_8/php-cgi.exe"
cmd := exec.Command(phpPath, "-b", fmt.Sprintf("%s:%d", address_php, port))
cmd.Env = append(os.Environ(),
"PHP_FCGI_CHILDREN=0", // Один процесс на порт
"PHP_FCGI_MAX_REQUESTS=1000", // Перезапуск после 1000 запросов
)
if !Сonsole_php {
cmd.Stdout = nil
cmd.Stderr = nil
}
err := cmd.Start()
if err != nil {
tools.Logs_file(1, "PHP", fmt.Sprintf("❌ Ошибка запуска FastCGI worker %d на порту %d: %v", workerID, port, err), "logs_php.log", true)
return
}
phpProcesses = append(phpProcesses, cmd)
tools.Logs_file(0, "PHP", fmt.Sprintf("✅ PHP FastCGI %d запущен на %s:%d", workerID, address_php, port), "logs_php.log", false)
// Ждём завершения процесса и перезапускаем
go func() {
cmd.Wait()
// Проверяем, не останавливается ли сервер
if stopping {
return // Не перезапускаем если сервер останавливается
}
tools.Logs_file(1, "PHP", fmt.Sprintf("⚠️ FastCGI worker %d завершился, перезапускаем...", workerID), "logs_php.log", true)
time.Sleep(1 * time.Second)
startFastCGIWorker(port, workerID) // Перезапуск
}()
}
// Получение следующего порта из пула (round-robin)
func getNextFCGIPort() int {
portMutex.Lock()
defer portMutex.Unlock()
port := fcgiPorts[portIndex]
portIndex = (portIndex + 1) % len(fcgiPorts)
return port
}
// Создание FastCGI пакета
func createFCGIPacket(requestType byte, requestID uint16, content []byte) []byte {
contentLength := len(content)
paddingLength := 8 - (contentLength % 8)
if paddingLength == 8 {
paddingLength = 0
}
header := FCGIHeader{
Version: FCGI_VERSION_1,
Type: requestType,
RequestID: requestID,
ContentLength: uint16(contentLength),
PaddingLength: byte(paddingLength),
Reserved: 0,
}
var buf bytes.Buffer
binary.Write(&buf, binary.BigEndian, header)
buf.Write(content)
buf.Write(make([]byte, paddingLength)) // Padding
return buf.Bytes()
}
// Кодирование FastCGI параметров
func encodeFCGIParams(params map[string]string) []byte {
var buf bytes.Buffer
for key, value := range params {
keyLen := len(key)
valueLen := len(value)
// Длина ключа
if keyLen < 128 {
buf.WriteByte(byte(keyLen))
} else {
binary.Write(&buf, binary.BigEndian, uint32(keyLen)|0x80000000)
}
// Длина значения
if valueLen < 128 {
buf.WriteByte(byte(valueLen))
} else {
binary.Write(&buf, binary.BigEndian, uint32(valueLen)|0x80000000)
}
// Ключ и значение
buf.WriteString(key)
buf.WriteString(value)
}
return buf.Bytes()
}
// HandlePHPRequest - универсальная функция для обработки файлов
// Проверяет является ли файл PHP и обрабатывает соответственно
// Возвращает true если файл был обработан (PHP или статический), false если нужна обработка ошибки
func HandlePHPRequest(w http.ResponseWriter, r *http.Request, host string, filePath string, originalURI string, originalPath string) bool {
// Импортируем path/filepath для проверки расширения
if filepath.Ext(filePath) == ".php" {
// Сохраняем оригинальные значения URL
originalURL := r.URL.Path
originalRawQuery := r.URL.RawQuery
// Устанавливаем путь к PHP файлу
r.URL.Path = filePath
// Вызываем существующий PHPHandler
PHPHandler(w, r, host, originalURI, originalPath)
// Восстанавливаем оригинальные значения
r.URL.Path = originalURL
r.URL.RawQuery = originalRawQuery
return true
} else {
// Это не PHP файл - обрабатываем как статический
fullPath := "WebServer/www/" + host + "/public_www" + filePath
http.ServeFile(w, r, fullPath)
return true
}
}
// PHPHandler с FastCGI
func PHPHandler(w http.ResponseWriter, r *http.Request, host string, originalURI string, originalPath string) {
phpPath := "WebServer/www/" + host + "/public_www" + r.URL.Path
// Проверяем существование файла
if _, err := os.Stat(phpPath); os.IsNotExist(err) {
http.ServeFile(w, r, "WebServer/tools/error_page/index.html")
tools.Logs_file(2, "PHP_404", "🔍 PHP файл не найден: "+phpPath, "logs_php.log", false)
return
}
// Получаем абсолютный путь для SCRIPT_FILENAME
absPath, err := filepath.Abs(phpPath)
if err != nil {
tools.Logs_file(1, "PHP", "❌ Ошибка получения абсолютного пути: "+err.Error(), "logs_php.log", false)
absPath = phpPath
}
// Получаем порт FastCGI
port := getNextFCGIPort()
// Подключаемся к FastCGI процессу
conn, err := net.DialTimeout("tcp", fmt.Sprintf("%s:%d", address_php, port), 5*time.Second)
if err != nil {
tools.Logs_file(1, "PHP", fmt.Sprintf("❌ Ошибка подключения к FastCGI порт %d: %v", port, err), "logs_php.log", false)
http.Error(w, "Service Unavailable", http.StatusServiceUnavailable)
return
}
defer conn.Close()
// Читаем POST данные
var postData []byte
if r.Method == "POST" {
postData, _ = io.ReadAll(r.Body)
r.Body.Close()
}
// Формируем параметры FastCGI
serverPort := "80"
if r.TLS != nil {
serverPort = "443"
}
// Используем переданные оригинальные значения или текущие если не переданы
requestURI := r.URL.RequestURI()
if originalURI != "" {
requestURI = originalURI
}
pathInfo := r.URL.Path
if originalPath != "" {
pathInfo = originalPath
}
params := map[string]string{
"REQUEST_METHOD": r.Method,
"REQUEST_URI": requestURI,
"QUERY_STRING": r.URL.RawQuery,
"CONTENT_TYPE": r.Header.Get("Content-Type"),
"CONTENT_LENGTH": fmt.Sprintf("%d", len(postData)),
"SCRIPT_FILENAME": absPath,
"SCRIPT_NAME": r.URL.Path,
"DOCUMENT_ROOT": "WebServer/www/" + host + "/public_www",
"SERVER_NAME": host,
"HTTP_HOST": host,
"SERVER_PORT": serverPort,
"SERVER_PROTOCOL": "HTTP/1.1",
"GATEWAY_INTERFACE": "CGI/1.1",
"REDIRECT_STATUS": "200",
"REMOTE_ADDR": strings.Split(r.RemoteAddr, ":")[0],
"REMOTE_HOST": strings.Split(r.RemoteAddr, ":")[0],
"PATH_INFO": pathInfo,
"PATH_TRANSLATED": absPath,
}
// Добавляем HTTP заголовки
for name, values := range r.Header {
if len(values) > 0 {
httpName := "HTTP_" + strings.ToUpper(strings.ReplaceAll(name, "-", "_"))
params[httpName] = values[0]
}
}
requestID := uint16(1)
// 1. Отправляем BEGIN_REQUEST
beginRequest := FCGIBeginRequestBody{
Role: FCGI_RESPONDER,
Flags: 0,
}
var beginBuf bytes.Buffer
binary.Write(&beginBuf, binary.BigEndian, beginRequest)
packet := createFCGIPacket(FCGI_BEGIN_REQUEST, requestID, beginBuf.Bytes())
conn.Write(packet)
// 2. Отправляем PARAMS с разбивкой на чанки
paramsData := encodeFCGIParams(params)
if len(paramsData) > 0 {
const maxChunkSize = 65535 // Максимальный размер FastCGI пакета
for offset := 0; offset < len(paramsData); offset += maxChunkSize {
end := offset + maxChunkSize
if end > len(paramsData) {
end = len(paramsData)
}
chunk := paramsData[offset:end]
packet = createFCGIPacket(FCGI_PARAMS, requestID, chunk)
conn.Write(packet)
}
}
// 3. Пустой PARAMS (конец параметров)
packet = createFCGIPacket(FCGI_PARAMS, requestID, []byte{})
conn.Write(packet)
// 4. Отправляем STDIN (POST данные) с разбивкой на чанки
if len(postData) > 0 {
const maxChunkSize = 65535 // Максимальный размер FastCGI пакета
for offset := 0; offset < len(postData); offset += maxChunkSize {
end := offset + maxChunkSize
if end > len(postData) {
end = len(postData)
}
chunk := postData[offset:end]
packet = createFCGIPacket(FCGI_STDIN, requestID, chunk)
conn.Write(packet)
}
}
// 5. Пустой STDIN (конец данных)
packet = createFCGIPacket(FCGI_STDIN, requestID, []byte{})
conn.Write(packet)
// Читаем ответ
response, err := readFastCGIResponse(conn, requestID)
if err != nil {
tools.Logs_file(1, "PHP", "❌ Ошибка чтения FastCGI ответа: "+err.Error(), "logs_php.log", false)
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
return
}
// Обрабатываем ответ
processPHPResponse(w, response)
tools.Logs_file(0, "PHP", fmt.Sprintf("✅ FastCGI обработал: %s (порт %d)", phpPath, port), "logs_php.log", false)
}
// Чтение FastCGI ответа
func readFastCGIResponse(conn net.Conn, requestID uint16) ([]byte, error) {
conn.SetReadDeadline(time.Now().Add(30 * time.Second))
var stdout bytes.Buffer
var stderr bytes.Buffer
for {
// Читаем заголовок FastCGI
headerBuf := make([]byte, 8)
_, err := io.ReadFull(conn, headerBuf)
if err != nil {
return nil, err
}
var header FCGIHeader
buf := bytes.NewReader(headerBuf)
binary.Read(buf, binary.BigEndian, &header)
// Читаем содержимое
content := make([]byte, header.ContentLength)
if header.ContentLength > 0 {
_, err = io.ReadFull(conn, content)
if err != nil {
return nil, err
}
}
// Читаем padding
if header.PaddingLength > 0 {
padding := make([]byte, header.PaddingLength)
io.ReadFull(conn, padding)
}
// Обрабатываем пакет
switch header.Type {
case FCGI_STDOUT:
if header.ContentLength > 0 {
stdout.Write(content)
} else {
// Пустой STDOUT означает конец
}
case FCGI_STDERR:
if header.ContentLength > 0 {
stderr.Write(content)
}
case FCGI_END_REQUEST:
// Завершение запроса
if stderr.Len() > 0 {
tools.Logs_file(1, "PHP", "FastCGI stderr: "+stderr.String(), "logs_php.log", false)
}
return stdout.Bytes(), nil
}
}
}
// Обработка PHP ответа (как раньше)
func processPHPResponse(w http.ResponseWriter, response []byte) {
responseStr := string(response)
// Разбираем заголовки и тело
parts := strings.SplitN(responseStr, "\r\n\r\n", 2)
if len(parts) < 2 {
parts = strings.SplitN(responseStr, "\n\n", 2)
}
if len(parts) >= 2 {
headers := strings.Split(parts[0], "\n")
statusCode := 200
for _, header := range headers {
header = strings.TrimSpace(header)
if header == "" {
continue
}
if strings.HasPrefix(strings.ToLower(header), "content-type:") {
contentType := strings.TrimSpace(strings.SplitN(header, ":", 2)[1])
w.Header().Set("Content-Type", contentType)
} else if strings.HasPrefix(strings.ToLower(header), "set-cookie:") {
cookie := strings.TrimSpace(strings.SplitN(header, ":", 2)[1])
w.Header().Add("Set-Cookie", cookie)
} else if strings.HasPrefix(strings.ToLower(header), "location:") {
location := strings.TrimSpace(strings.SplitN(header, ":", 2)[1])
w.Header().Set("Location", location)
w.WriteHeader(http.StatusFound)
return
} else if strings.HasPrefix(strings.ToLower(header), "status:") {
status := strings.TrimSpace(strings.SplitN(header, ":", 2)[1])
if code, err := strconv.Atoi(strings.Split(status, " ")[0]); err == nil {
statusCode = code
}
} else if strings.Contains(header, ":") {
headerParts := strings.SplitN(header, ":", 2)
if len(headerParts) == 2 {
w.Header().Set(strings.TrimSpace(headerParts[0]), strings.TrimSpace(headerParts[1]))
}
}
}
w.WriteHeader(statusCode)
w.Write([]byte(parts[1]))
} else {
w.Header().Set("Content-Type", "text/html; charset=utf-8")
w.Write(response)
}
}
// PHP_Stop останавливает все FastCGI процессы
func PHP_Stop() {
// Устанавливаем флаг остановки
stopping = true
for i, cmd := range phpProcesses {
if cmd != nil && cmd.Process != nil {
err := cmd.Process.Kill()
if err != nil {
tools.Logs_file(1, "PHP", fmt.Sprintf("❌ Ошибка остановки FastCGI процесса %d: %v", i, err), "logs_php.log", true)
} else {
tools.Logs_file(0, "PHP", fmt.Sprintf("✅ FastCGI процесс %d остановлен", i), "logs_php.log", false)
}
}
}
phpProcesses = nil
fcgiPorts = nil
// Дополнительно убиваем все процессы php-cgi.exe
cmd := exec.Command("taskkill", "/F", "/IM", "php-cgi.exe")
cmd.Run()
tools.Logs_file(0, "PHP", "🛑 Все FastCGI процессы остановлены", "logs_php.log", true)
}