Files
waf-platform/EdgeDNS/internal/nodes/http_writer.go
2026-02-04 20:27:13 +08:00

180 lines
4.1 KiB
Go

// Copyright 2023 GoEdge CDN goedge.cdn@gmail.com. All rights reserved. Official site: https://goedge.cn .
package nodes
import (
"encoding/json"
"errors"
"github.com/iwind/TeaGo/Tea"
"github.com/iwind/TeaGo/types"
"github.com/miekg/dns"
"net"
"net/http"
"reflect"
)
type HTTPWriter struct {
rawConn net.Conn
rawWriter http.ResponseWriter
contentType string
}
func NewHTTPWriter(rawWriter http.ResponseWriter, rawConn net.Conn, contentType string) *HTTPWriter {
return &HTTPWriter{
rawWriter: rawWriter,
rawConn: rawConn,
contentType: contentType,
}
}
func (this *HTTPWriter) LocalAddr() net.Addr {
return this.rawConn.LocalAddr()
}
func (this *HTTPWriter) RemoteAddr() net.Addr {
return this.rawConn.RemoteAddr()
}
func (this *HTTPWriter) WriteMsg(msg *dns.Msg) error {
if msg == nil {
return errors.New("'msg' should not be nil")
}
msgData, err := this.encodeMsg(msg)
if err != nil {
return err
}
this.rawWriter.Header().Set("Content-Length", types.String(len(msgData)))
this.rawWriter.Header().Set("Content-Type", this.contentType)
// cache-control
if len(msg.Answer) > 0 {
var minTtl uint32
for _, answer := range msg.Answer {
var header = answer.Header()
if header != nil && header.Ttl > 0 && (minTtl == 0 || header.Ttl < minTtl) {
minTtl = header.Ttl
}
}
if minTtl > 0 {
this.rawWriter.Header().Set("Cache-Control", "max-age="+types.String(minTtl))
}
}
this.rawWriter.WriteHeader(http.StatusOK)
_, err = this.rawWriter.Write(msgData)
return err
}
func (this *HTTPWriter) Write(p []byte) (int, error) {
this.rawWriter.Header().Set("Content-Length", types.String(len(p)))
this.rawWriter.WriteHeader(http.StatusOK)
return this.rawWriter.Write(p)
}
func (this *HTTPWriter) Close() error {
return nil
}
func (this *HTTPWriter) TsigStatus() error {
return nil
}
func (this *HTTPWriter) TsigTimersOnly(timersOnly bool) {
}
func (this *HTTPWriter) Hijack() {
hijacker, ok := this.rawWriter.(http.Hijacker)
if ok {
_, _, _ = hijacker.Hijack()
}
}
func (this *HTTPWriter) encodeMsg(msg *dns.Msg) ([]byte, error) {
if this.contentType == "application/x-javascript" || this.contentType == "application/json" {
var result = map[string]any{
"Status": 0,
"TC": msg.Truncated,
"RD": msg.RecursionDesired,
"RA": msg.RecursionAvailable,
"AD": msg.AuthenticatedData,
"CD": msg.CheckingDisabled,
}
// questions
var questionMaps = []map[string]any{}
for _, question := range msg.Question {
questionMaps = append(questionMaps, map[string]any{
"name": question.Name,
"type": question.Qtype,
})
}
result["Question"] = questionMaps
// answers
var answerMaps = []map[string]any{}
for _, answer := range msg.Answer {
var answerMap = map[string]any{
"name": answer.Header().Name,
"type": answer.Header().Rrtype,
"TTL": answer.Header().Ttl,
}
switch x := answer.(type) {
case *dns.A:
answerMap["data"] = x.A.String()
case *dns.AAAA:
answerMap["data"] = x.AAAA.String()
case *dns.CNAME:
answerMap["data"] = x.Target
case *dns.TXT:
answerMap["data"] = x.Txt
case *dns.NS:
answerMap["data"] = x.Ns
case *dns.MX:
answerMap["data"] = x.Mx
answerMap["preference"] = x.Preference
default:
var answerValue = reflect.ValueOf(answer).Elem()
var answerType = answerValue.Type()
var countFields = answerType.NumField()
for i := 0; i < countFields; i++ {
var fieldName = answerType.Field(i).Name
var fieldValue = answerValue.FieldByName(fieldName)
if !fieldValue.IsValid() {
continue
}
var fieldInterface = fieldValue.Interface()
if fieldInterface == nil {
continue
}
_, ok := fieldInterface.(dns.RR_Header)
if ok {
continue
}
if countFields == 2 {
answerMap["data"] = fieldValue.Interface()
} else {
answerMap[fieldName] = fieldValue.Interface()
}
}
}
answerMaps = append(answerMaps, answerMap)
}
result["Answer"] = answerMaps
if Tea.IsTesting() {
return json.MarshalIndent(result, "", " ")
} else {
return json.Marshal(result)
}
} else {
return msg.Pack()
}
}