Files
akmon/doc_zhipao/uts_akreq_head_request_support.md
2026-01-20 08:04:15 +08:00

7.2 KiB
Raw Blame History

UTS AkReq HEAD 请求支持说明

概述

为了正确支持 Supabase 的 head: true 选项,我们对 ak-req.uts 进行了增强,使其能够正确处理 HEAD 请求的特殊响应格式。

HEAD 请求的特点

HTTP 标准

  • HEAD 请求与 GET 请求相同,但只返回响应头,不返回响应体
  • 适用于只需要元数据而不需要实际内容的场景
  • 用于检查资源状态、获取缓存信息、计算文件大小等

Supabase 中的应用

  • { head: true } 选项会使用 HEAD 请求
  • 响应体为空,但 Content-Range header 包含 count 信息
  • 极大减少网络传输量

AkReq 的增强实现

修改前的问题

// 原始实现:不区分 HEAD 和 GET
success: (res) => {
    // 总是尝试解析 res.data即使是 HEAD 请求
    let data: UTSJSONObject | Array<any> | null;
    if (typeof res.data == 'string') {
        // ... 解析逻辑
    }
}

修改后的正确处理

success: (res) => {
    // HEAD 请求特殊处理:没有响应体,只有 headers
    if (options.method === 'HEAD') {
        const result = {
            status: res.statusCode,
            data: null,  // HEAD 请求没有数据
            headers: res.header as UTSJSONObject,
            error: null
        } as AkReqResponse<any>
        resolve(result);
        return;
    }
    
    // 其他请求正常解析 data
    // ...
}

完整的数据流

1. 发起 HEAD 请求

const result = await supa
  .from('ak_assignments')
  .select('*', { count: 'exact', head: true })
  .eq('teacher_id', userId)
  .execute()

2. AkSupa 构建请求

// aksupa.uts
let httpMethod = 'GET';
if (options != null && options.head == true) {
    httpMethod = 'HEAD';  // 使用 HEAD 方法
}

let reqOptions: AkReqOptions = {
    url,
    method: httpMethod,  // 'HEAD'
    headers: {
        'Prefer': 'count=exact,return=minimal'
    }
}

3. AkReq 处理响应

// ak-req.uts
if (options.method === 'HEAD') {
    return {
        status: 200,
        data: null,        // HEAD 请求没有响应体
        headers: {         // 重要信息在 headers 中
            'content-range': '0-0/42',  // count 信息
            'content-type': 'application/json'
        },
        error: null
    }
}

4. AkSupa 解析 count

// aksupa.uts
// 从 content-range header 解析 count
let contentRange = res.headers?.get('content-range')
if (contentRange != null) {
    const match = /\/(\d+)$/.exec(contentRange);
    if (match != null) {
        total = parseInt(match[1] ?? "0");  // 提取 count
    }
}

// HEAD 模式返回 count
if (this._options.head == true) {
    return {
        data: null,
        count: total,   // 解析出的 count
        total,
        // ...
    };
}

5. 应用层使用

// dashboard.uvue
const result = await supa.from('ak_assignments')
  .select('*', { count: 'exact', head: true })
  .execute()

const count = result.total  // 42

网络请求对比

GET 请求(大量数据传输)

Request:
GET /rest/v1/ak_assignments?select=*&count=exact&teacher_id=eq.123

Response:
HTTP/1.1 200 OK
Content-Range: 0-9/42
Content-Type: application/json

[
  {"id": 1, "title": "作业1", "description": "...", ...},
  {"id": 2, "title": "作业2", "description": "...", ...},
  // ... 更多数据
]

HEAD 请求(最小数据传输)

Request:
HEAD /rest/v1/ak_assignments?select=*&count=exact&teacher_id=eq.123

Response:
HTTP/1.1 200 OK
Content-Range: 0-0/42
Content-Type: application/json

(无响应体)

性能优势

传输量对比

请求类型 响应体大小 网络传输 解析开销
GET ~50KB
HEAD 0KB 极低

实际场景

// 统计查询场景
const stats = await Promise.all([
  supa.from('assignments').select('*', { count: 'exact', head: true }).execute(),
  supa.from('students').select('*', { count: 'exact', head: true }).execute(),
  supa.from('courses').select('*', { count: 'exact', head: true }).execute()
])

// 如果用 GET可能传输几百KB数据
// 使用 HEAD只传输几KB headers

错误处理

HEAD 请求可能的错误

const safeHeadRequest = async (queryBuilder: any) => {
  try {
    const result = await queryBuilder.execute()
    
    // HEAD 请求成功但没有 count 信息
    if (result.data === null && result.total === 0) {
      console.warn('HEAD 请求可能没有正确解析 count')
    }
    
    return result.total ?? 0
  } catch (error) {
    console.error('HEAD 请求失败:', error)
    return 0
  }
}

调试 HEAD 请求

const debugHeadRequest = async () => {
  const result = await supa
    .from('ak_assignments')
    .select('*', { count: 'exact', head: true })
    .execute()
  
  console.log('HEAD 请求结果:')
  console.log('- Status:', result.status)
  console.log('- Data:', result.data)        // 应该为 null
  console.log('- Total:', result.total)      // 应该有数值
  console.log('- Headers:', result.headers)  // 包含 content-range
  
  return result
}

最佳实践

1. 统计查询使用 HEAD

// ✅ 推荐:统计查询使用 HEAD
const getAssignmentCount = async (teacherId: string) => {
  const result = await supa
    .from('ak_assignments')
    .select('*', { count: 'exact', head: true })
    .eq('teacher_id', teacherId)
    .execute()
  
  return result.total ?? 0
}

2. 数据查询使用 GET

// ✅ 推荐:需要数据时使用 GET
const getAssignmentList = async (teacherId: string) => {
  const result = await supa
    .from('ak_assignments')
    .select('*', { count: 'exact' })  // 不使用 head
    .eq('teacher_id', teacherId)
    .limit(10)
    .execute()
  
  return {
    data: result.data,
    total: result.total
  }
}

3. 混合场景

// 先获取总数,再按需获取数据
const loadDashboard = async (teacherId: string) => {
  // 1. 快速获取统计HEAD
  const [totalCount, completedCount] = await Promise.all([
    supa.from('ak_assignments').select('*', { count: 'exact', head: true }).eq('teacher_id', teacherId).execute(),
    supa.from('ak_assignments').select('*', { count: 'exact', head: true }).eq('teacher_id', teacherId).eq('status', 'completed').execute()
  ])
  
  // 2. 根据需要获取具体数据GET
  const recentData = await supa
    .from('ak_assignments')
    .select('*')
    .eq('teacher_id', teacherId)
    .order('created_at', { ascending: false })
    .limit(5)
    .execute()
  
  return {
    stats: {
      total: totalCount.total,
      completed: completedCount.total
    },
    recent: recentData.data
  }
}

总结

通过增强 ak-req.uts 对 HEAD 请求的支持:

  1. 正确处理HEAD 请求不解析响应体,直接返回 data: null
  2. 性能优化:大幅减少网络传输量
  3. 符合标准:遵循 HTTP HEAD 请求规范
  4. 无缝集成:与现有 Supabase count 功能完美配合

这使得 UTS 应用能够高效地进行数据统计查询,特别适合仪表板、统计页面等场景。