Кабинет: страницы собирает ядро, на хостинге — ретранслятор (Go и PHP)
- modules/lk: конверт запроса/ответа (PageRequest/PageResponse), ServeEnvelope — тот же обработчик страниц внутри ядра; клиент к API внутри процесса (Config.Local/LocalKey); режим ретранслятора (Config.Relay, BJ_RENDER=core, выбор в установщике и lk.json) - ядро: встроенный кабинет (lkcabinet.go), POST /api/lk/render для прямого режима, цикл режима ожидания выполняет конверты страниц; API через ретранслятор недостижимо - PHP-ретранслятор для обычного хостинга (modules/lk/relay-php: index.php, .htaccess, README): установщик в браузере, прямой режим через curl, режим ожидания через очередь в SQLite (BEGIN IMMEDIATE, busy_timeout), конфиг в bj-lk-data рядом с сайтом или в data под .htaccess; архив со страницы настроек (/admin/setup/lk/relay-php.zip) - настройки: карточка НПР/УДС перенесена из раздела «Лицензия» в «Экспорт QUIK»; кнопка «Обновить с сервера лицензий» видна при любом ключе - инструкция /admin/help/lk: два варианта размещения, шаги для PHP-хостинга - тесты: TestLKRelayRendersInCore (оба режима), TestLKPHPRelayAgentMode (настоящий PHP против цикла ядра, PHP_BIN), проверка безопасности через PHP-ретранслятор 34/34 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Q4YVo4UMopqT5DzicqpPjL
This commit is contained in:
+24
-41
@@ -14,7 +14,6 @@ package lk
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"net/http"
|
||||
@@ -23,25 +22,11 @@ import (
|
||||
"time"
|
||||
)
|
||||
|
||||
// agentTask — один запрос кабинета к ядру, ожидающий ответа.
|
||||
// agentTask — один запрос к ядру, ожидающий ответа: вызов API (старый кабинет) или
|
||||
// страница целиком в конверте (ретранслятор).
|
||||
type agentTask struct {
|
||||
ID string `json:"id"`
|
||||
Method string `json:"method"`
|
||||
Path string `json:"path"`
|
||||
Body []byte `json:"body,omitempty"`
|
||||
Session string `json:"session,omitempty"`
|
||||
// Адрес и браузер посетителя: ядро привязывает к ним сессию и считает по адресу
|
||||
// попытки входа. В прямом режиме они идут заголовками, здесь — полями запроса.
|
||||
ClientIP string `json:"client_ip,omitempty"`
|
||||
ClientAgent string `json:"client_agent,omitempty"`
|
||||
|
||||
done chan agentResult
|
||||
}
|
||||
|
||||
// agentResult — ответ ядра.
|
||||
type agentResult struct {
|
||||
Status int `json:"status"`
|
||||
Body []byte `json:"body,omitempty"`
|
||||
PageRequest
|
||||
done chan PageResponse
|
||||
}
|
||||
|
||||
// agentQueue — очередь запросов к ядру и ожидающие ответа.
|
||||
@@ -66,14 +51,21 @@ func newAgentQueue() *agentQueue {
|
||||
return &agentQueue{waiting: map[string]*agentTask{}, signal: make(chan struct{}, 1)}
|
||||
}
|
||||
|
||||
// push — поставить запрос в очередь и ждать ответа ядра.
|
||||
func (q *agentQueue) push(ctx context.Context, method, path string, body []byte, session string, wait time.Duration) (agentResult, error) {
|
||||
// push — вызов API через очередь (кабинет с отрисовкой на своём сервере).
|
||||
func (q *agentQueue) push(ctx context.Context, method, path string, body []byte, session string, wait time.Duration) (PageResponse, error) {
|
||||
env := PageRequest{Method: method, Path: path, Body: body, Session: session}
|
||||
if v, ok := ctx.Value(visitorKey{}).(visitor); ok {
|
||||
env.ClientIP, env.ClientAgent = v.ip, v.agent
|
||||
}
|
||||
return q.pushEnvelope(ctx, env, wait)
|
||||
}
|
||||
|
||||
// pushEnvelope — поставить конверт в очередь и ждать ответа ядра.
|
||||
func (q *agentQueue) pushEnvelope(ctx context.Context, env PageRequest, wait time.Duration) (PageResponse, error) {
|
||||
q.mu.Lock()
|
||||
q.seq++
|
||||
t := &agentTask{ID: idFromSeq(q.seq), Method: method, Path: path, Body: body, Session: session, done: make(chan agentResult, 1)}
|
||||
if v, ok := ctx.Value(visitorKey{}).(visitor); ok {
|
||||
t.ClientIP, t.ClientAgent = v.ip, v.agent
|
||||
}
|
||||
env.ID = idFromSeq(q.seq)
|
||||
t := &agentTask{PageRequest: env, done: make(chan PageResponse, 1)}
|
||||
q.queue = append(q.queue, t)
|
||||
q.waiting[t.ID] = t
|
||||
q.mu.Unlock()
|
||||
@@ -88,10 +80,10 @@ func (q *agentQueue) push(ctx context.Context, method, path string, body []byte,
|
||||
return res, nil
|
||||
case <-ctx.Done():
|
||||
q.forget(t.ID)
|
||||
return agentResult{}, ctx.Err()
|
||||
return PageResponse{}, ctx.Err()
|
||||
case <-timer.C:
|
||||
q.forget(t.ID)
|
||||
return agentResult{}, errors.New("ядро не ответило: проверьте, что bj-server подключён к кабинету")
|
||||
return PageResponse{}, errors.New("ядро не ответило: проверьте, что bj-server подключён к кабинету")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -126,7 +118,7 @@ func (q *agentQueue) next(ctx context.Context, wait time.Duration) *agentTask {
|
||||
}
|
||||
|
||||
// reply — вернуть ответ ядра ожидающему запросу.
|
||||
func (q *agentQueue) reply(id string, res agentResult) bool {
|
||||
func (q *agentQueue) reply(id string, res PageResponse) bool {
|
||||
q.mu.Lock()
|
||||
t, ok := q.waiting[id]
|
||||
delete(q.waiting, id)
|
||||
@@ -178,28 +170,19 @@ func (s *Server) registerAgent(mux *http.ServeMux) {
|
||||
return
|
||||
}
|
||||
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||||
_ = json.NewEncoder(w).Encode(map[string]any{
|
||||
"id": t.ID, "method": t.Method, "path": t.Path, "session": t.Session,
|
||||
"client_ip": t.ClientIP, "client_agent": t.ClientAgent,
|
||||
"body": base64.StdEncoding.EncodeToString(t.Body),
|
||||
})
|
||||
_ = json.NewEncoder(w).Encode(t.PageRequest)
|
||||
})
|
||||
// Ядро возвращает ответ на запрос.
|
||||
mux.HandleFunc("/agent/result", func(w http.ResponseWriter, r *http.Request) {
|
||||
if !keyOK(w, r) {
|
||||
return
|
||||
}
|
||||
var in struct {
|
||||
ID string `json:"id"`
|
||||
Status int `json:"status"`
|
||||
Body string `json:"body"`
|
||||
}
|
||||
if json.NewDecoder(http.MaxBytesReader(w, r.Body, 8<<20)).Decode(&in) != nil || in.ID == "" {
|
||||
var in PageResponse
|
||||
if json.NewDecoder(http.MaxBytesReader(w, r.Body, 32<<20)).Decode(&in) != nil || in.ID == "" {
|
||||
http.Error(w, "не разобрали ответ", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
body, _ := base64.StdEncoding.DecodeString(in.Body)
|
||||
if !s.agentQueue().reply(in.ID, agentResult{Status: in.Status, Body: body}) {
|
||||
if !s.agentQueue().reply(in.ID, in) {
|
||||
w.WriteHeader(http.StatusGone) // запрос уже не ждут: клиент ушёл или истёк срок
|
||||
return
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user