tencent cloud

文档反馈

推送至 HTTP 服务器

最后更新时间:2024-06-28 14:21:49
    EdgeOne 实时日志推送支持将日志推送至自定义接口地址,您可通过控制台或 API 进行配置。EdgeOne 可通过 HTTP POST 请求调用您提供的后端接口地址,将日志在 HTTP Body 中传输到您指定的服务器上。

    操作步骤

    1. 登录 边缘安全加速平台 EO 控制台,在左侧菜单栏中,单击站点列表,在站点列表内单击需配置的站点,进入站点详情页面。
    2. 在站点详情页面,单击日志服务 > 实时日志。
    3. 在实时日志页面,单击新建推送任务。
    4. 在选择日志源页面,填写任务名称、选择日志类型、服务区域、需推送日志的域名/四层代理实例,单击下一步
    5. 在定义推送内容页面:
    (必选)在预设字段列表中勾选需要推送的日志字段;
    (可选)添加自定义日志字段,支持从请求头、响应头、Cookie 头中提取指定字段名称的值;
    (可选)配置推送日志筛选条件,默认推送全量日志;
    (可选)在高级配置中,配置采样比例,默认不开启采样,推送 100% 日志至目的地。
    (可选)在高级配置中,配置日志输出格式,默认格式为 JSON Lines。
    6. 在选择目的地页面,选择 HTTP 服务(POST),单击下一步
    7. 在目的地信息页面,填写相关目的地及参数信息。
    参数名称
    说明
    接口地址
    填入您的日志接收接口地址,例如:https://www.example.com/edgeone-logs
    内容压缩
    为减少日志内容的大小,节约流量开销,您可以通过勾选使用 gzip 压缩日志文件开启内容压缩,EdgeOne 将会使用 gzip 格式压缩日志后再传输日志,并且会增加 HTTP 头部Content-Encoding: gzip来标明压缩格式。
    源站鉴权
    选择为加密鉴权时,推送日志时将携带鉴权信息供源站进行验证,保证数据来源身份的安全性。鉴权算法见:鉴权算法参考
    自定义 HTTP 请求头
    添加需要 EdgeOne 发起请求时携带的 HTTP 头部。例如:
    通过添加头部 log-source: EdgeOne 来识别日志来源为 EdgeOne。
    通过添加头部BatchSize: ${batchSize}来获取每次 POST 请求内推送的日志条数。
    说明
    若您填写的头部名称为 Content-Type 等 EdgeOne 日志推送默认携带的头部,那么您填写的头部值将覆盖默认值。
    8. 单击推送,在弹窗中确认相关费用提示,单击确定创建
    9. 实时日志推送任务在配置阶段为了校验接口连通性,将向接口地址发送测试数据进行验证,数据格式如下所示:
    { "ClientState": "CH-AH", "EdgeResponseTime": 366, "RequestID": "13515444256055847385", "ClientRegion": "CN", "RemotePort": 443, "RequestHost": "www.tencent.com", "RequestMethod": "GET", "RequestUrlQueryString": "-", "RequestUrl": "/en-us/about.html", "RequestProtocol": "HTTP/2.0", "EdgeServerID": "336d5ebc5436534e61d16e63ddfca327-d41d8cd98f00b204e9800998ecf8427e", "RequestTime": "2022-07-01T02:37:13Z", "EdgeCacheStatus": "-", "EdgeResponseBytes": 39430, "EdgeResponseStatusCode": 200, "ClientIP": "0.0.0.0", "RequestReferer": "https://www.tencent.com/", "RequestUA": "Mozilla/5.0 (iPhone; CPU iPhone OS 15_5 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/15.5 Mobile/15E148 Safari/604.1", "EdgeServerIP": "0.0.0.0", "RequestRange": "0-100/200", "EdgeInternalTime": 334, "RequestBytes": 237 }

    相关参考

    服务端解析日志代码示例

    当您未开启源站鉴权时,可参考以下 Python 代码在服务端解析请求正文中的日志内容。
    # 导入Python标准库中的模块
    import time # 用于获取当前时间
    import gzip # 用于处理gzip压缩的数据
    # 从http.server模块导入HTTPServer和BaseHTTPRequestHandler类
    from http.server import HTTPServer, BaseHTTPRequestHandler
    import json # 用于处理JSON数据格式
    # 定义一个继承自BaseHTTPRequestHandler的类,用于处理HTTP请求
    class Resquest(BaseHTTPRequestHandler):
    # 重写do_POST方法,该方法会在服务器接收到POST请求时被调用
    def do_POST(self):
    # 打印请求头信息
    print(str(self.headers))
    # 打印HTTP请求的命令(如POST)
    print(self.command)
    # 读取请求体内容,根据请求头中的Content-Length字段确定读取的长度
    req_datas = self.rfile.read(int(self.headers['content-length']))
    try:
    # 尝试解码请求体内容并打印
    print(req_datas.decode())
    except Exception as e:
    # 如果解码过程中发生异常,打印异常信息
    print(e)
    # 检查请求头中是否有Content-Encoding: gzip,如果有,则解压请求体
    if self.headers['Content-Encoding'] == 'gzip':
    data = gzip.decompress(req_datas)
    # 打印解压后的gzip内容
    print('---------------decompress gzip content-------------------------')
    print(data.decode())
    # 检查请求的路径是否为 '/edgeone-logs',如果不是,则返回404错误
    if self.path != '/edgeone-logs':
    self.send_error(404, "Page not Found!")
    return
    # 如果请求路径正确,准备响应数据
    data = {
    'result_code': '1',
    'result_desc': 'Success',
    'timestamp': int(time.time()) # 响应当前时间戳
    }
    # 发送HTTP响应状态码200,表示请求成功
    self.send_response(200)
    # 设置响应头Content-type为application/json
    self.send_header('Content-type', 'application/json')
    # 结束响应头的发送
    self.end_headers()
    # 将响应数据以JSON格式写入到响应体中
    self.wfile.write(json.dumps(data).encode('utf-8'))
    # 检查当前脚本是否作为主程序运行
    if __name__ == '__main__':
    # 定义服务器监听的地址和端口,您可将9002替换为自定义端口
    host = ('', 9002)
    # 创建HTTPServer对象,传入监听地址和端口以及处理请求的请求处理器类
    server = HTTPServer(host, Resquest)
    # 打印服务器启动信息
    print("Starting server, listen at: %s:%s" % host)
    # 启动服务器,使其持续运行直到外部中断
    server.serve_forever()

    请求鉴权算法

    如果您在推送目的地信息中,源站鉴权内选择了加密签名,可输入您自定义配置 SecretId 和 SecretKey,EdgeOne 将在请求 URL 中增加签名auth_keyaccess_key,签名算法详情如下:
    1. 请求 URL 构成
    如下所示,请求 URL 将在后携带 auth_keyaccess_key
    http://DomainName[:port]/[uri]?auth_key=timestamp-rand-md5hash&access_key=SecretId
    参数说明:
    timestamp:请求当前时间,使用 Unix 秒级10位时间戳。
    rand:随机数。
    access_key:用于标识接口请求方的身份,即您所自定义配置的 SecretId。
    SecretKey:固定长度 32 位,即您所自定义配置的 SecretKey。
    uri:资源标识符,例如:/access_log/post
    md5hash:md5hash = md5sum(string_to_sign),其中 string_to_sign ="uri-timestamp-rand-SecretKey"。通过md5算法计算出的验证串,数字0-9和小写英文字母 a-z 混合,固定长度为32个字符。
    2. 计算示例
    假定填入参数为: 接口地址:https://www.example.com/access_log/post SecretId = YourID SecretKey = YourKey uri = /access_log/post timestamp = 1571587200 rand = 0
    string_to_sign = "/access_log/post-1571587200-0-YourKey"
    基于该字符串计算出:
    md5hash=md5sum("/access_log/post-1571587200-0-YourKey")=1f7ffa7bff8f06bbfbe2ace0f14b7e16
    最终推送时的请求 url 为:
    https://www.example.com/cdnlog/post?auth_key=1571587200-0-1f7ffa7bff8f06bbfbe2ace0f14b7e16&access_key=YourID
    服务端在接收到推送请求后,提取auth_key的值. 对 auth_key 的值进行拆分,获取timestamprandmd5hash。可先检查 timestamp 是否过期,过期时间建议为300s,并基于上述规则拼装加密字符串,利用 SecretKey 拼装出需加密的字符串,加密后与 auth_key 中的 md5hash 值进行比较,相同则说明鉴权通过。
    3. 服务端解析鉴权请求代码示例
    Python
    Golang
    import hashlib
    
    from flask import Flask, request
    
    app = Flask(__name__)
    
    
    def get_rsp(msg, result={}, code=0):
    return {
    "respCode": code,
    "respMsg": msg,
    "result": result
    }
    
    
    def get_secret_key(access_key):
    return "secret_key"
    
    
    @app.route("/access_log/post", methods=['POST'])
    def access_log():
    if request.method == 'POST':
    if request.content_type.startswith('application/json'):
    current_time_ts, rand_num, md5hash = request.args.get("auth_key").split("-")
    # 判断请求时间是否是在有效期内
    if time.time() - int(current_time_ts) > 300:
    return get_rsp(msg="The request is out of time", code=-1)
    
    access_key = request.args.get("access_key")
    # 通过access_key(SecretId)获取secret_key
    secret_key = get_secret_key(access_key)
    raw_str = "%s-%s-%s-%s" % (request.path, current_time_ts, rand_num, secret_key)
    auth_md5hash = hashlib.md5(raw_str.encode("utf-8")).hexdigest()
    if auth_md5hash == md5hash:
    # 认证通过
    if request.headers['content-encoding'] == 'gzip':
    # 解压数据
    pass
    # 数据处理
    return get_rsp("ok")
    return get_rsp(msg="Please use content_type by application/json", code=-1)
    return get_rsp(msg="The request method not find, method == %s" % request.method, code=-1)
    
    if __name__ == '__main__':
    app.run(host='0.0.0.0', port=8888, debug=True)
    
    package main
    
    import (
    "context"
    "crypto/md5"
    "fmt"
    "log"
    "net/http"
    "os"
    "os/signal"
    "strings"
    "syscall"
    )
    
    func main() {
    mux := http.NewServeMux()
    mux.Handle("/access_log/post", &logHandler{})
    
    server := &http.Server{
    Addr: ":5000",
    Handler: mux,
    }
    
    // 创建系统信号接收器
    done := make(chan os.Signal)
    signal.Notify(done, os.Interrupt, syscall.SIGINT, syscall.SIGTERM)
    go func() {
    <-done
    
    if err := server.Shutdown(context.Background()); err != nil {
    log.Fatal("Shutdown server:", err)
    }
    }()
    
    err := server.ListenAndServe()
    if err != nil {
    if err == http.ErrServerClosed {
    log.Print("Server closed under request")
    } else {
    log.Fatal("Server closed unexpected")
    }
    }
    }
    
    type logHandler struct{}
    
    func (*logHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    if r.Method == "POST" {
    query := r.URL.Query()
    authKey := query.Get("auth_key")
    accessKey := query.Get("access_key")//access_key 即您提供的SecretId
    authKeys := strings.Split(authKey, "-")
    if len(authKeys) == 3 {
    currentTimeTs := authKeys[0]
    //进行时间戳有效期判断
    RandNum := authKeys[1]
    md5Hash := authKeys[2]
    secretKey := getSecretKey(accessKey)
    authStr := fmt.Sprintf("%s-%s-%s-%s", "/access_log/post", currentTimeTs, RandNum, secretKey)
    data := []byte(authStr)
    has := md5.Sum(data)
    authMd5 := fmt.Sprintf("%x", has) //转换成字符串进行比较
    if authMd5 == md5Hash {
    // todo 认证成功
    if r.Header.Get("Content-Encoding") == "gzip" {
    //解压数据
    }
    //数据处理
    }
    } else {
    //异常处理
    }
    }
    }
    
    // 获取SecretKey
    func getSecretKey(accessKey string) string {
    if accessKey != "" {
    // 通过Access_key(SecretI)获取Secret_Key
    return "secret_key"
    }
    return ""
    }
    
    
    联系我们

    联系我们,为您的业务提供专属服务。

    技术支持

    如果你想寻求进一步的帮助,通过工单与我们进行联络。我们提供7x24的工单服务。

    7x24 电话支持