v0.1.35: query-mode XML-ответы, TOCTOU receive, валидации AWS, purge 60s, сужение локов
This commit is contained in:
+63
-15
@@ -7,7 +7,6 @@ import (
|
||||
"encoding/json"
|
||||
"encoding/xml"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
@@ -103,10 +102,35 @@ func requestBodyLimitMiddleware(next http.Handler) http.Handler {
|
||||
})
|
||||
}
|
||||
|
||||
// encodeResponse — сериализует ответ в формате протокола клиента.
|
||||
//
|
||||
// Логическая схема:
|
||||
// - AwsJsonProtocol (чистый JSON-протокол): успешный ответ — JSON тела;
|
||||
// ОШИБКА — спец-формат AWS-JSON {"__type": "com.amazonaws.sqs#<Code>",
|
||||
// "message": "..."}. Раньше для ErrorResponse писался голый ErrorResult
|
||||
// ({"Type":...,"Code":...,"Message":...}) — SDK не распознавал это как ошибку
|
||||
// и возвращал Code=None.
|
||||
// - AwsQueryProtocol: ответ всегда XML (в т.ч. <ErrorResponse> для ошибок).
|
||||
func encodeResponse(w http.ResponseWriter, req *http.Request, statusCode int, body interfaces.AbstractResponseBody) {
|
||||
protocol := resolveProtocol(req)
|
||||
switch protocol {
|
||||
case AwsJsonProtocol:
|
||||
// Ошибка в JSON-протоколе: __type + message — единственный формат,
|
||||
// который botocore распознаёт как исключение с кодом.
|
||||
if errResp, ok := body.(models.ErrorResponse); ok {
|
||||
w.Header().Set("x-amzn-RequestId", errResp.RequestId)
|
||||
w.Header().Set("Content-Type", "application/x-amz-json-1.0")
|
||||
w.WriteHeader(statusCode)
|
||||
err := json.NewEncoder(w).Encode(map[string]string{
|
||||
"__type": "com.amazonaws.sqs#" + errResp.Result.Code,
|
||||
"message": errResp.Result.Message,
|
||||
})
|
||||
if err != nil {
|
||||
log.Errorf("Response Encoding Error: %v\nResponse: %+v", err, body)
|
||||
}
|
||||
return
|
||||
}
|
||||
// Успешный ответ: JSON тела (Result).
|
||||
w.Header().Set("x-amzn-RequestId", body.GetRequestId())
|
||||
w.Header().Set("Content-Type", "application/x-amz-json-1.0")
|
||||
w.WriteHeader(statusCode)
|
||||
@@ -196,9 +220,16 @@ func actionHandler(w http.ResponseWriter, req *http.Request) {
|
||||
}
|
||||
return
|
||||
}
|
||||
// Неизвестный Action — отвечаем ошибкой в ФОРМАТЕ ПРОТОКОЛА клиента
|
||||
// (XML ErrorResponse для Query, AWS-JSON для JSON-протокола).
|
||||
// Раньше возвращался голый текст "Bad Request" (200/400 без AWS-XML) —
|
||||
// SDK получал ClientError с Code=None и не мог понять причину.
|
||||
log.Warnf("Bad Request - Action: %s", action)
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
io.WriteString(w, "Bad Request")
|
||||
errType := models.SqsErrors["GeneralError"]
|
||||
encodeResponse(w, req, errType.StatusCode(), models.ErrorResponse{
|
||||
Result: errType.Response(),
|
||||
RequestId: "00000000-0000-0000-0000-000000000000",
|
||||
})
|
||||
}
|
||||
|
||||
type AwsProtocol int
|
||||
@@ -208,25 +239,42 @@ const (
|
||||
AwsQueryProtocol AwsProtocol = iota
|
||||
)
|
||||
|
||||
// extractAction — извлекает Action из запроса (Query Protocol или JSON Protocol)
|
||||
// extractAction — извлекает имя операции SQS из запроса.
|
||||
//
|
||||
// Логическая схема (не зависит от resolveProtocol):
|
||||
// 1. Заголовок X-Amz-Target ("AmazonSQS.SendMessage") — используют клиенты
|
||||
// JSON-протокола И AWS CLI v2 в query-mode. Извлекаем часть после точки.
|
||||
// 2. Иначе — form-параметр Action (классический query-протокол).
|
||||
//
|
||||
// ВАЖНО: определение по X-Amz-Target в первую очередь обязательно — AWS CLI v2
|
||||
// шлёт Content-Type: application/x-amz-json-1.0 + x-amzn-query-mode: true, но
|
||||
// Action у него в заголовке, а НЕ в form-параметрах.
|
||||
func extractAction(req *http.Request) string {
|
||||
protocol := resolveProtocol(req)
|
||||
switch protocol {
|
||||
case AwsJsonProtocol:
|
||||
action := req.Header.Get("X-Amz-Target")
|
||||
if action := req.Header.Get("X-Amz-Target"); action != "" {
|
||||
parts := strings.SplitN(action, ".", 2)
|
||||
if len(parts) != 2 || parts[1] == "" {
|
||||
return ""
|
||||
if len(parts) == 2 && parts[1] != "" {
|
||||
return parts[1]
|
||||
}
|
||||
return parts[1]
|
||||
case AwsQueryProtocol:
|
||||
return req.FormValue("Action")
|
||||
}
|
||||
return ""
|
||||
return req.FormValue("Action")
|
||||
}
|
||||
|
||||
// resolveProtocol — определяет протокол по Content-Type
|
||||
// resolveProtocol — определяет протокол ОТВЕТА по заголовкам запроса.
|
||||
//
|
||||
// Логическая схема:
|
||||
// 1. Заголовок x-amzn-query-mode: true (шлют AWS CLI v2 и boto3) означает
|
||||
// «query-compatible JSON»: тело запроса сериализовано как JSON, но клиент
|
||||
// ЖДЁТ ответ в XML (query-протокол). Если на такой запрос ответить JSON —
|
||||
// botocore не распарсит тело ошибки (ClientError с Code=None — именно это
|
||||
// наблюдалось: «сырые 400/404» без AWS-XML). Поэтому для query-mode
|
||||
// принудительно отвечаем XML.
|
||||
// 2. Content-Type: application/x-amz-json-1.0 БЕЗ query-mode — чистый
|
||||
// JSON-протокол (SDK): и запрос, и ответ в JSON.
|
||||
// 3. Всё остальное (form-urlencoded от старых клиентов) — query/XML.
|
||||
func resolveProtocol(req *http.Request) AwsProtocol {
|
||||
if req.Header.Get("x-amzn-query-mode") == "true" {
|
||||
return AwsQueryProtocol
|
||||
}
|
||||
if req.Header.Get("Content-Type") == "application/x-amz-json-1.0" {
|
||||
return AwsJsonProtocol
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user