VB.Net中HttpClient调用NetAtmo API GET请求始终报错求助
我今年74岁,从未接触过Web开发,甚至不清楚HTTP GET与POST的区别。在VB.Net中使用HttpClient调用NetAtmo的getmeasure接口时始终返回错误,但程序生成的URL与NetAtmo官方文档生成的完全一致:
程序生成的URL:https://api.netatmo.com/api/getmeasure?device_id=70%3Aee%3A50%3A12%3A4d%3A12&module_id=02%3A00%3A00%3A12%3A86%3A30&scale=30min&type=temperature&date_begin=1690468462&date_end=1690469662&limit=1024&optimize=false&real_time=true
官方文档生成的URL:https://api.netatmo.com/api/getmeasure?device_id=70%3Aee%3A50%3A12%3A4d%3A12&module_id=02%3A00%3A00%3A12%3A86%3A30&scale=30min&type=temperature&date_begin=1690465917&date_end=1690467117&limit=1024&optimize=false&real_time=true
并且以下CURL命令可正常执行:
curl -X GET "https://api.netatmo.com/api/getmeasure?device_id=70%3Aee%3A50%3A12%3A4d%3A12&scale=30min&type=temperature&date_begin=1690468462&date_end=1690469662&limit=1024&optimize=false&real_time=true" -H "accept: application/json" -H "Authorization: Bearer xxxxxxxxxxxxxxxxxxxxxx|xxxxxxxxxxxxxxxxxxxxxxxx"
Bearer已做替换。我不确定是否需要Authorization,但替换为我生成的URL后CURL能正常工作,问题应该出在HttpClient.GetAsync上。获取刷新token的HTTP POST请求可正常工作,但GET请求一直失败,尝试改用HTTP POST结果也相同。我认为不是AccessToken的问题,因为这类问题会有明确提示。
相关代码
类代码
Public Class ClassHttpPostNetAtmo ReadOnly myClientHttp As HttpClient ReadOnly parametres As New Dictionary(Of String, String) Dim builder As UriBuilder Dim query As Specialized.NameValueCollection ReadOnly dateUnix As New DateTime(1970, 1, 1, 0, 0, 0, 0) Public Sub New() myClientHttp = New HttpClient() End Sub Public Sub ClearPostParametres() parametres.Clear() End Sub Public Sub AddPostParametre(ByVal key As String, ByVal value As String) parametres.Add(key, value) End Sub Public Async Function PostHttp(ByVal RequestUri As String) As Task(Of String) Dim content As New FormUrlEncodedContent(parametres) Dim response As HttpResponseMessage = Await myClientHttp.PostAsync(RequestUri, content) Dim responseString As String = Await response.Content.ReadAsStringAsync() Return responseString End Function Public Async Function RefreshToken(ByVal vRequestUri As String, ByVal vRefresh_token As String, ByVal vClient_id As String, ByVal vClient_secret As String) As Task(Of Dictionary(Of String, String)) ClearPostParametres() AddPostParametre("grant_type", "refresh_token") AddPostParametre("refresh_token", vRefresh_token) AddPostParametre("client_id", vClient_id) AddPostParametre("client_secret", vClient_secret) Dim responseString As String = Await PostHttp(String.Concat(vRequestUri, "/oauth2/token")) Dim paramRep As New Dictionary(Of String, String) Dim str() As String = responseString.Replace("{", "").Replace("}", "").Split(",") For Each strPar As String In str Dim par() As String = strPar.Split(":") paramRep.Add(par(0).Replace("""", ""), par(1).Replace("""", "")) Next Return paramRep End Function Public Sub ClearGetParameter(ByVal vRequestUri As String) builder = New UriBuilder(vRequestUri) With { .Port = -1 } query = HttpUtility.ParseQueryString(builder.Query) End Sub Public Sub AddGetParametre(ByVal key As String, ByVal value As String) If value IsNot Nothing Then If key = "type" Then Dim tvalue() As String = value.Split(",") For Each strVal As String In tvalue query.Add(key, strVal) Next Else query.Add(key, value) End If End If End Sub Public Async Function GetHttp() As Task(Of String) builder.Query = query.ToString() Dim url As String = builder.ToString() Dim response As HttpResponseMessage = Await myClientHttp.GetAsync(url) If response.ReasonPhrase = "ok" Then Dim responseString As String = Await response.Content.ReadAsStringAsync() Return responseString Else Throw New Exception(String.Concat("Erreur Http GET : ", response.ReasonPhrase, " Status code = ", response.StatusCode)) End If End Function Public Async Function GetWeatherData(ByVal vRequestUri As String, ByVal vDeviceId As String, vDateBegin As DateTime, ByVal vDateEnd As DateTime, Optional ByVal vModuleId As String = Nothing, Optional ByVal vScale As String = "30min", Optional ByVal vType As String = "temperature", Optional ByVal vLimit As String = "1024", Optional ByVal vOptimize As String = "false", Optional ByVal vRealTime As String = "true") As Task ClearGetParameter(String.Concat(vRequestUri, "/api/getmeasure")) AddGetParametre("device_id", vDeviceId) AddGetParametre("module_id", vModuleId) AddGetParametre("scale", vScale) AddGetParametre("type", vType) AddGetParametre("date_begin", CType(((vDateBegin.Subtract(dateUnix)).TotalSeconds), Integer).ToString()) AddGetParametre("date_end", CType(((vDateEnd.Subtract(dateUnix)).TotalSeconds), Integer).ToString()) AddGetParametre("limit", vLimit) AddGetParametre("optimize", vOptimize) AddGetParametre("real_time", vRealTime) Dim responseString As String = Await GetHttp() End Function End Class
调用代码
Imports ClassLibraryHttpPostNetAtmo Imports LibraryDivers Public Class FrmTestMyLibraryNetAtmo Dim ClientId As String Dim ClientSecret As String Dim myNetAtmo As New ClassHttpPostNetAtmo Dim AccessToken As String Dim RefreshToken As String Private Sub FrmTestMyLibraryNetAtmo_Shown(sender As Object, e As EventArgs) Handles MyBase.Shown End Sub Private Async Sub BtnTest_Click(sender As Object, e As EventArgs) Handles BtnTest.Click If (My.Settings.AccessToken Is Nothing OrElse My.Settings.AccessToken = "") Or (My.Settings.RefreshToken Is Nothing OrElse My.Settings.RefreshToken = "") Or (My.Settings.DateRefreshToken < Now) Then ClientId = ClassDivers.Decode(My.Settings.ClientId, My.Settings.EncodeNumber) ClientSecret = ClassDivers.Decode(My.Settings.ClientSecret, My.Settings.EncodeNumber) Dim par As Dictionary(Of String, String) = Await myNetAtmo.RefreshToken(My.Settings.RequestUri, My.Settings.RefreshToken, ClientId, ClientSecret) AccessToken = par("access_token") RefreshToken = par("refresh_token") My.Settings.AccessToken = par("access_token") My.Settings.RefreshToken = par("refresh_token") My.Settings.DateRefreshToken = Now.AddSeconds(par("expire_in")).AddMinutes(-10) My.Settings.Save() End If Await myNetAtmo.GetWeatherData(My.Settings.RequestUri, "70:ee:50:12:4d:12", Now.AddMinutes(-20), Now, vModuleId:="02:00:00:12:86:30") End Sub End Class
问题排查与解决
1. 核心问题:缺失Authorization请求头
你的CURL命令里明确携带了Authorization: Bearer <token>认证头,但当前HttpClient的GET请求完全没有添加这个头。NetAtmo的所有接口都需要通过Bearer Token验证身份,这是导致请求失败的关键原因。
2. 具体修复步骤
步骤1:给ClassHttpPostNetAtmo添加设置认证头的方法
在类中新增以下代码,用于设置Authorization请求头:
Public Sub SetAuthorizationHeader(ByVal accessToken As String) myClientHttp.DefaultRequestHeaders.Authorization = New System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", accessToken) End Sub
步骤2:在调用GetWeatherData前设置认证头
修改BtnTest_Click中的代码,在调用GetWeatherData之前添加认证头设置:
' ... 原有刷新token的代码 ... myNetAtmo.SetAuthorizationHeader(AccessToken) Await myNetAtmo.GetWeatherData(My.Settings.RequestUri, "70:ee:50:12:4d:12", Now.AddMinutes(-20), Now, vModuleId:="02:00:00:12:86:30")
步骤3:显式设置Accept请求头
虽然HttpClient默认可能会发送Accept头,但显式设置为application/json更稳妥,修改类的构造函数:
Public Sub New() myClientHttp = New HttpClient() myClientHttp.DefaultRequestHeaders.Accept.Add(New System.Net.Http.Headers.MediaTypeWithQualityHeaderValue("application/json")) End Sub
3. 辅助排查:优化异常信息
修改GetHttp方法,捕获接口返回的具体错误内容,方便后续定位问题:
Public Async Function GetHttp() As Task(Of String) builder.Query = query.ToString() Dim url As String = builder.ToString() Dim response As HttpResponseMessage = Await myClientHttp.GetAsync(url) Dim responseString As String = Await response.Content.ReadAsStringAsync() If response.IsSuccessStatusCode Then Return responseString Else Throw New Exception(String.Concat("Erreur Http GET : ", response.ReasonPhrase, " Status code = ", response.StatusCode, " Response content: ", responseString)) End If End Function
内容的提问来源于stack exchange,提问作者frousset

