HarmonyOS 鸿蒙Next中http设置请求头失败

HarmonyOS 鸿蒙Next中http设置请求头失败 有个本地端业务,使用第三方库的GitHub · Where software is built webserver库实现的后端和,现在有个问题出现 我使用无论是第三方的axios还有鸿蒙原生的http都会出现 后端接收不到请求headers 还有 body,但是去请求JAVA或者C#等部署的后端服务器可以正常接收到 ,我是用js版本的axios还有postman去请求webserver的后端也是正常的能看到请求头,有知道怎么解决这个问题的吗,附上我的http代码

import { http } from '@kit.NetworkKit';
import { BusinessError } from '@kit.BasicServicesKit';
import { FontLog } from "../utils/FontLog";
import { GlobalPreferences } from '../common/PreferencesConfig';
import { JSON } from '@kit.ArkTS';

let mlog = new FontLog("axios");
const TOKEN_KEY = 'auth_token';
const DEFAULT_TOKEN = 'IP63mOM4Pe1dE/ONgJAYuAu9nGCsqWCYDbOPc7QV35wymg0yTFHTqeEGBYAoYR2z';

interface ResponseData {
  code?: number;
  data?: ESObject;
  message?: string;
}

interface ProxyConfig {
  host: string;
  port: number;
  username?: string;
  password?: string;
}

interface HttpClientConfig {
  baseURL: string;
  timeout: number;
  headers: ESObject;
  proxy?: ProxyConfig; // 代理配置
  usingProxy?: boolean; // 是否使用代理
}

export interface HttpResponse<T> {
  data: T;
  status: number;
  statusText: string;
  headers: ESObject;
  config: HttpClientConfig;
}

function getToken(): string {
  try {
    const gp = GlobalPreferences.getInstance();
    const token = gp.getSync<string>(TOKEN_KEY, DEFAULT_TOKEN);
    return token;
  } catch (error) {
    return DEFAULT_TOKEN;
  }
}

function buildURL(baseURL: string, url: string, params?: ESObject): string {
  let fullURL = baseURL;
  if (url) {
    if (url.startsWith('http://') || url.startsWith('https://')) {
      fullURL = url;
    } else {
      // 移除 baseURL 末尾的斜杠
      const base = baseURL.endsWith('/') ? baseURL.slice(0, -1) : baseURL;
      // 移除 url 开头的斜杠
      const path = url.startsWith('/') ? url : '/' + url;
      fullURL = base + path;
    }
  }

  if (params) {
    const keys = Object.keys(params);
    if (keys.length > 0) {
      const queryParams: string[] = [];
      for (let i = 0; i < keys.length; i++) {
        const key = keys[i];
        const value: ESObject = params[key] as ESObject;
        if (value !== undefined && value !== null) {
          queryParams.push(`${encodeURIComponent(key)}=${encodeURIComponent(String(value))}`);
        }
      }
      if (queryParams.length > 0) {
        fullURL += (fullURL.indexOf('?') > -1 ? '&' : '?') + queryParams.join('&');
      }
    }
  }

  return fullURL;
}

function mergeHeaders(defaultHeaders: ESObject, configHeaders?: ESObject): Record<string, string> {
  const merged: Record<string, string> = {};

  if (defaultHeaders) {
    const defaultKeys = Object.keys(defaultHeaders);
    for (let i = 0; i < defaultKeys.length; i++) {
      const key = defaultKeys[i];
      const value: ESObject = defaultHeaders[key] as ESObject;
      // 确保所有值都是字符串类型
      merged[key] = value !== undefined && value !== null ? String(value) : '';
    }
  }

  if (configHeaders) {
    const configKeys = Object.keys(configHeaders);
    for (let i = 0; i < configKeys.length; i++) {
      const key = configKeys[i];
      const value: ESObject = configHeaders[key] as ESObject;
      // 确保所有值都是字符串类型,并覆盖默认值
      merged[key] = value !== undefined && value !== null ? String(value) : '';
    }
  }

  return merged;
}

interface RequestInterceptorResult {
  url: string;
  headers: Record<string, string>;
  data?: string;
}

function requestInterceptor(config: HttpClientConfig, url: string, data?: ESObject, params?: ESObject,
  headers?: ESObject): RequestInterceptorResult {
  const token = getToken();

  const mergedHeaders: Record<string, string> = mergeHeaders(config.headers, headers);
  // 确保所有 header 值都是字符串类型
  mergedHeaders['Content-Type'] = 'application/json;charset=UTF-8';
  mergedHeaders['Authorization'] = `Bearer ${token}`;
  mergedHeaders['authorization'] = `Bearer ${token}`;

  const fullURL = buildURL(config.baseURL, url, params);

  // 构建固定格式的请求体:{token, data: {}}
  const requestBody: ESObject = {
    token: token,
    data: data || {}
  };

  const requestData: string = JSON.stringify(requestBody);

  const requestInfo: ESObject = {
    method: 'POST',
    url: fullURL,
    body: requestBody,
    params: params,
    headers: mergedHeaders
  };

  // 打印请求头(调试信息)
  mlog.info(`[请求头] ${JSON.stringify(mergedHeaders)}`);
  // 打印请求URL(调试信息)
  mlog.info(`[请求URL] ${fullURL}`);
  // 打印请求body(调试信息)
  mlog.info(`[请求Body] ${requestData}`);

  const result: RequestInterceptorResult = {
    url: fullURL,
    headers: mergedHeaders,
    data: requestData
  };

  return result;
}

function responseInterceptor(response: http.HttpResponse, config: HttpClientConfig,
  url: string): HttpResponse<ESObject> {
  let responseData: ESObject = {};

  try {
    const resultValue: ESObject = response.result as ESObject;
    if (resultValue && typeof resultValue === 'string') {
      responseData = JSON.parse(resultValue) as ESObject;
    } else if (resultValue && typeof resultValue === 'object') {
      responseData = resultValue as ESObject;
    }
  } catch (error) {
    const errorValue: Error = error as Error;
    const errorObj: ESObject = {
      message: errorValue.message || String(errorValue),
      error: errorValue
    };
    mlog.error(`[响应解析错误] ${JSON.stringify(errorObj)}`);
  }

  const responseInfo: ESObject = {
    status: response.responseCode,
    statusText: '',
    url: url,
    method: 'POST',
    data: responseData
  };

  const httpResponse: HttpResponse<ESObject> = {
    data: responseData,
    status: response.responseCode,
    statusText: '',
    headers: response.header || {},
    config: config
  };

  if (response.responseCode === 200) {
    if (responseData && typeof responseData === 'object') {
      const data = responseData as ResponseData;
      if (data.code !== undefined) {
        if (data.code === 0 || data.code === 200) {
          return httpResponse;
        } else {
          mlog.error(`[业务错误] code=${data.code}, message=${data.message || '未知错误'}`);
          const error = new Error(`业务错误: code=${data.code}, message=${data.message || '未知错误'}`);
          (error as ESObject)['response'] = httpResponse;
          throw error;
        }
      }
    }
    return httpResponse;
  } else {
    mlog.error(`[响应错误] status=${response.responseCode}`);
    const error = new Error(`响应错误: status=${response.responseCode}`);
    (error as ESObject)['response'] = httpResponse;
    throw error;
  }
}

function makeRequest<T = ESObject>(
  config: HttpClientConfig,
  url: string,
  data?: ESObject,
  params?: ESObject,
  headers?: ESObject
): Promise<HttpResponse<T>> {
  return new Promise((resolve, reject) => {
    // 每一个httpRequest对应一个HTTP请求任务,不可复用
    const httpRequest = http.createHttp();
    const requestConfig = requestInterceptor(config, url, data, params, headers);

    // 用于订阅HTTP响应头,此接口会比request请求先返回。可以根据业务需要订阅此消息
    // 从API 8开始,使用on('headersReceive', Callback)替代on('headerReceive', AsyncCallback)。 8+
    httpRequest.on('headersReceive', (header) => {
      mlog.info(`[响应头] ${JSON.stringify(header)}`);
    });

    // 使用 addHeader 方法添加请求头
    const headerKeys = Object.keys(requestConfig.headers);
    for (let i = 0; i < headerKeys.length; i++) {
      const key = headerKeys[i];
      const value = requestConfig.headers[key];
      if (value !== undefined && value !== null) {
        // 使用类型断言,因为类型定义可能不完整,但运行时支持此方法
        (httpRequest as ESObject).addHeader(key, String(value));
      }
    }

    // 所有请求都使用 POST 方法
    // 构建请求选项
    const httpRequestOptions: http.HttpRequestOptions = {
      method: http.RequestMethod.POST,
      readTimeout: config.timeout,
      connectTimeout: config.timeout,
      expectDataType: http.HttpDataType.STRING,
      usingCache: true,
      priority: 1,
      extraData: requestConfig.data, // 请求体数据
      usingProxy: config.usingProxy !== undefined ? config.usingProxy : false // 是否使用代理
    };

    // 如果配置了代理,记录代理信息(鸿蒙系统会自动使用系统代理配置)
    if (config.proxy && config.usingProxy) {
      mlog.info(`[代理配置] host=${config.proxy.host}, port=${config.proxy.port}`);
      // 注意:鸿蒙的 HTTP 请求会使用系统级别的代理配置
      // 如果需要自定义代理,可能需要通过系统网络设置或使用其他网络库
    }

    httpRequest.request(requestConfig.url, httpRequestOptions,
      (err: BusinessError | null, response: http.HttpResponse | null) => {
        if (err) {
          // 取消订阅HTTP响应头事件
          httpRequest.off('headersReceive');
          // 当该请求使用完毕时,务必调用destroy方法主动销毁
          httpRequest.destroy();

          const errObj: ESObject = err as ESObject;
          const errorInfo: ESObject = {
            message: (errObj['message'] as string) || JSON.stringify(errObj),
            code: (errObj['code'] as number) || 0,
            url: requestConfig.url,
            method: 'POST'
          };

          mlog.error(`[请求失败] POST ${errorInfo.url}`);
          mlog.error(`[错误信息] ${JSON.stringify(errorInfo)}`);

          reject(errObj);
          return;
        }

        if (!response) {
          // 取消订阅HTTP响应头事件
          httpRequest.off('headersReceive');
          // 当该请求使用完毕时,务必调用destroy方法主动销毁
          httpRequest.destroy();

          const error: ESObject = {
            message: '响应为空',
            url: requestConfig.url,
            method: 'POST'
          };
          reject(error);
          return;
        }

        try {
          const httpResponse: HttpResponse<ESObject> = responseInterceptor(response, config, url);
          // 取消订阅HTTP响应头事件
          httpRequest.off('headersReceive');
          // 当该请求使用完毕时,务必调用destroy方法主动销毁
          httpRequest.destroy();
          resolve(httpResponse as HttpResponse<T>);
        } catch (error) {
          // 取消订阅HTTP响应头事件
          httpRequest.off('headersReceive');
          // 当该请求使用完毕时,务必调用destroy方法主动销毁
          httpRequest.destroy();

          const errorValue: Error = error as Error;
          const errorObj: ESObject = {
            message: errorValue.message || String(errorValue),
            error: errorValue
          };
          reject(errorObj);
        }
      });
  });
}

export interface HttpRequestConfig {
  url: string;
  data?: ESObject; // 传入的对象,会被封装到 {token, data: {}} 的 data 字段中
  params?: ESObject; // URL 查询参数
  headers?: ESObject; // 额外的请求头
}

class AxiosHttpRequest {
  private config: HttpClientConfig;

  constructor(config: HttpClientConfig) {
    this.config = config;
  }

  /**
   * 发送 POST 请求
   * 请求体格式固定为:{token, data: {}}
   * token 会自动从配置中获取并封装
   * @param requestConfig 请求配置
   * @returns Promise<HttpResponse<T>>
   */
  post<T = ESObject>(requestConfig: HttpRequestConfig): Promise<HttpResponse<T>> {
    // 如果传入的 data 是一个对象,直接使用;如果是其他格式,包装成对象
    let requestData: ESObject = {};
    if (requestConfig.data) {
      // 如果 data 已经是对象,直接使用
      if (typeof requestConfig.data === 'object' && requestConfig.data !== null) {
        requestData = requestConfig.data;
      } else {
        // 如果是其他类型,包装成对象
        requestData = { value: requestConfig.data };
      }
    }

    return makeRequest<T>(
      this.config,
      requestConfig.url,
      requestConfig.params,
      requestConfig.headers,
      requestData // 这个 data 会被封装到 {token, data: {}} 的 data 字段中
    );
  }
}

type HttpPromise<T> = Promise<HttpResponse<T>>;

const axiosClientConfig: HttpClientConfig = {
  baseURL: "http://localhost:8080/pk",
  timeout: 10 * 1000,
  headers: {
    'Content-Type': 'application/json;charset=UTF-8',
    'Authorization': "Bearer IP63mOM4Pe1dE/ONgJAYuAu9nGCsqWCYDbOPc7QV35yeDR3teLPaxA222BRXgTZS"
  },
  // 代理配置(可选)
  // 启用代理:设置为 true 以使用系统代理配置
  usingProxy: false,
  // 自定义代理配置(如果需要)
  proxy: {
    host: 'http://192.168.1.83:8080/pk',
    port: 8081,
  }
};

const axiosClient = new AxiosHttpRequest(axiosClientConfig);

export { axiosClient };

export type { HttpPromise };

更多关于HarmonyOS 鸿蒙Next中http设置请求头失败的实战教程也可以访问 https://www.itying.com/category-93-b0.html

4 回复

请求头合并逻辑优化

function mergeHeaders(...) {
  // 确保合并时保留原始Headers的完整性
  const merged: Record<string, string> = { ...defaultHeaders };
  
  // 覆盖逻辑改为保留已有值
  if (configHeaders) {
    for (const key in configHeaders) {
      if (configHeaders[key] !== undefined) {
        merged[key] = String(configHeaders[key]);
      }
    }
  }
  return merged;
}

请求体构建策略调整

// 移除强制嵌套结构,根据后端要求选择直接传递data
const requestBody: ESObject = data || {};
const requestData: string = JSON.stringify(requestBody);

试试

更多关于HarmonyOS 鸿蒙Next中http设置请求头失败的实战系列教程也可以访问 https://www.itying.com/category-93-b0.html


你好,定位到是下面代码问题, httpRequest 并没有 addHeader 方法,所以这里调用时抛出了异常。导致后面代码没有执行,请求没有发出去。

const headerKeys = Object.keys(requestConfig.headers);
   for (let i = 0; i < headerKeys.length; i++) {
     const key = headerKeys[i];
     const value = requestConfig.headers[key];
     if (value !== undefined && value !== null) {
       // 使用类型断言,因为类型定义可能不完整,但运行时支持此方法
       (httpRequest as ESObject).addHeader(key, String(value));
     }
   }

添加自定义header,是在http.HttpRequestOptions里面。

const httpRequestOptions: http.HttpRequestOptions = {
  method: http.RequestMethod.POST,
  readTimeout: config.timeout,
  connectTimeout: config.timeout,
  ...
  header: header //你的headers
};

在HarmonyOS Next中,HTTP请求头设置失败通常与网络权限配置或API使用方式有关。请检查以下两点:

  1. 网络权限:确保在module.json5文件中已正确声明ohos.permission.INTERNET权限。

  2. API使用:使用@ohos.net.http模块时,需通过http.createHttp()创建请求对象,然后使用request.setHeader()方法设置请求头。确保在调用request.request()发起请求前完成设置。

示例代码片段:

let httpRequest = http.createHttp();
httpRequest.setHeader('Content-Type', 'application/json');
// ... 其他设置
httpRequest.request(url, options, (err, data) => {});

检查代码逻辑,确保设置请求头的步骤未被跳过或覆盖。

根据你的描述和代码,问题可能出在以下几个方面:

  1. 请求头设置方式问题:在HarmonyOS Next的@kit.NetworkKit中,设置请求头应该使用httpRequestOptions.header字段,而不是addHeader方法。你的代码中使用了类型断言调用addHeader,这可能不是标准用法。

  2. 请求体格式问题:你的代码将请求体构建为{token, data: {}}格式,但实际发送时使用的是extraData字段。需要确认WebServer后端期望的请求体格式。

建议修改如下

// 修改请求选项配置,使用header字段设置请求头
const httpRequestOptions: http.HttpRequestOptions = {
  method: http.RequestMethod.POST,
  readTimeout: config.timeout,
  connectTimeout: config.timeout,
  expectDataType: http.HttpDataType.STRING,
  usingCache: true,
  priority: 1,
  extraData: requestConfig.data,
  usingProxy: config.usingProxy !== undefined ? config.usingProxy : false,
  // 正确设置请求头
  header: requestConfig.headers
};

// 删除addHeader循环,直接使用上面的header配置
httpRequest.request(requestConfig.url, httpRequestOptions, ...);
  1. Content-Type重复设置:你在代码中多次设置了Content-Type,确保最终使用的是正确的值。

  2. 本地网络访问权限:确认应用已申请网络权限,在module.json5中添加:

{
  "module": {
    "requestPermissions": [
      {
        "name": "ohos.permission.INTERNET"
      }
    ]
  }
}
  1. WebServer兼容性:由于WebServer是第三方库,可能需要检查它是否对HarmonyOS的HTTP客户端有特殊要求。可以尝试在WebServer端打印原始请求信息来调试。

  2. 使用系统原生方式:考虑直接使用@kit.NetworkKit的标准示例代码,避免过多的封装层,以排除代码逻辑问题。

建议先简化代码,使用最基本的HTTP请求测试WebServer是否能正常接收请求头和请求体,再逐步添加业务逻辑。

回到顶部