Невозможно выполнить действительный вызов GET с помощью HttpClientC#

Место общения программистов C#
Ответить
Anonymous
 Невозможно выполнить действительный вызов GET с помощью HttpClient

Сообщение Anonymous »

Я пытаюсь написать простое консольное приложение для связи с моим термостатом Honeywell. Они предлагают бесплатный REST API, описанный здесь: https://developer.honeywellhome.com/. У меня возникли проблемы с простым вызовом GET сразу после аутентификации, и я не знаю, что делаю неправильно. Я надеялся, что кто-нибудь сможет мне помочь.
Подводя итог, мой процесс состоит из трех шагов:
  • Зарегистрируйте приложение. чтобы получить AppID и секрет (здесь все хорошо).
  • Пройдите аутентификацию с помощью OAuth2, чтобы получить токен доступа (здесь все хорошо).
  • Вызовите любой REST API, использующий предоставленный токен доступа (проблема здесь).
Подробнее
Моя консоль csproj очень проста:

Код: Выделить всё



Exe
net8.0
true
enable



1. Зарегистрируйте приложение, чтобы получить AppID и секрет.

Приложение регистрируется здесь, это довольно стандартно: https://developer.honeywellhome.com/user/me/ apps
В этом примере предположим, что Resideo предоставляет мне следующие значения, которые я буду использовать позже:
  • AppId: ABCD1234
  • Секрет: WXYZ9876

2. Пройдите аутентификацию, чтобы получить токен доступа.

Структура ответа json токена доступа описана здесь: https://developer.honeywellhome.com/aut ... uth2/apis/ post/accesstoken
Вот как я определяю класс для десериализации json:

Код: Выделить всё

internal class ResideoToken
{
[JsonPropertyName("refresh_token_expires_in")]
public string RefreshTokenExpiration { get; set; } = string.Empty;
[JsonPropertyName("api_product_list")]
public string ApiProductList { get; set; } = string.Empty;
[JsonPropertyName("organization_name")]
public string OrganizationName { get; set; } = string.Empty;
[JsonPropertyName("developer.email")]
public string DeveloperEmail { get; set; } = string.Empty;
[JsonPropertyName("token_type")]
public string TokenType { get; set; } = string.Empty;
[JsonPropertyName("issued_at")]
public string IssuedAt { get; set; } = string.Empty;
[JsonPropertyName("client_id")]
public string ClientId { get; set; } = string.Empty;
[JsonPropertyName("access_token")]
public string AccessToken { get; set; } = string.Empty;
[JsonPropertyName("application_name")]
public string ApplicationName { get; set; } = string.Empty;
[JsonPropertyName("scope")]
public string Scope { get; set; } = string.Empty;
[JsonPropertyName("expires_in")]
public string ExpiresIn { get; set; } = string.Empty;
[JsonPropertyName("refresh_count")]
public string RefreshCount { get; set; } = string.Empty;
[JsonPropertyName("status")]
public string Status { get; set; } = string.Empty;
}
И вот как я успешно аутентифицируюсь:

Код: Выделить всё

string appId = "ABCD1234";
string secret = "WXYZ9876";
HttpClient client = new()
{
BaseAddress = new Uri(uriString: "https://api.honeywell.com/", uriKind: UriKind.Absolute)
};

KeyValuePair[] encodedContentCollection =
[
new("Content-Type", "application/x-www-form-urlencoded"),
new("grant_type", "client_credentials")
];
HttpRequestMessage request = new(HttpMethod.Post, "oauth2/accesstoken")
{
Content = new FormUrlEncodedContent(encodedContentCollection)
};

string base64AppIdAndSecret = Convert.ToBase64String(Encoding.UTF8.GetBytes($"{appId}:{secret}"));
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", base64AppIdAndSecret);

HttpResponseMessage response = await client.SendAsync(request, HttpCompletionOption.ResponseHeadersRead);

response.EnsureSuccessStatusCode(); // Should throw if not 200-299

Stream responseContentStream = await response.Content.ReadAsStreamAsync();

ResideoToken token = await JsonSerializer.DeserializeAsync(responseContentStream, JsonSerializerOptions.Default) ??
throw new Exception("Could not deserialize response stream to a ResideoToken");

3. Вызовите любой REST API, используя предоставленный токен доступа.

Самый простой случай, который я нашел, — это получить список местоположений и устройств с помощью метода GET и передачи одного параметра: https:/ /developer.honeywellhome.com/lyric/apis/get/locations

Код: Выделить всё

// I had this originally, but it was incorrect -> client.DefaultRequestHeaders.Add("Bearer", token.AccessToken);
client.DefaultRequestHeaders.Authentication = new AuthenticationHeaderValue("Bearer", token.AccessToken);

client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));

// Base URL has already been established in the client
// According to the instructions, the apikey is the AppID
HttpResponseMessage locationResponse = await client.GetAsync($"v2/locations?apikey={appId}");

locationResponse.EnsureSuccessStatusCode(); // This is failing with 401 unauthorized

// I am never able to reach this
string result = await locationResponse.Content.ReadAsStringAsync();
Console.WriteLine($"Locations: {result}");
Как видите, вызов GetAsync завершается с ошибкой 401. Это исключение:

Код: Выделить всё

Unhandled exception.  System.Net.Http.HttpRequestException: Response status code does not indicate success: 401 (Unauthorized).
at System.Net.Http.HttpResponseMessage.EnsureSuccessStatusCode()
Это странно, потому что если я выведу строку base64, сгенерированную моим кодом, на консоль, скопирую ее и использую в вызове Curl, все будет успешно:

Код: Выделить всё

$ curl -X GET --header "Authorization: Bearer AbCdEfGhIjKlMnOpQrStUvWxYz==" "https://api.honeywell.com/v2/locations?apikey=ABCD1234"

# This prints a huge valid json file describing all the details of the locations and my devices as described in https://developer.honeywellhome.com/lyric/apis/get/locations
Вопросы
На третьем этапе возникают все проблемы, поэтому я не уверен в следующем:

[*]Правильно ли я повторно использую строку токена доступа, полученную в результате успешной аутентификации?
[*]Правильно ли я повторно использую HttpClient? Насколько я понимаю, рекомендуется продолжать использовать один и тот же экземпляр для одной и той же группы запросов, идентифицированных с одной и той же аутентификацией.
[*]Я устанавливаю заголовок Bearer в правильном месте в качестве заголовка по умолчанию или мне следует вручную создать запрос и установить там заголовок? Если второе, то как мне это сделать?
[*]Устанавливаю ли я для параметра Accept media type заголовка запроса по умолчанию допустимое значение «application/json»? Если нет, то где мне его нужно поместить?
[*]Вызывают ли параметры по умолчанию, изначально установленные в HttpClient при аутентификации, какие-либо проблемы с последующими вызовами запросов? Другими словами, нужно ли мне, например, очищать заголовки по умолчанию?
[*]Передаю ли я правильный URL-адрес вызову GetAsync? Он не содержит базовый URL-адрес.
[*]Правильно ли установить параметр GET (apikey=1234ABCD) непосредственно в строке URL-адреса GetAsync? Если нет, то как правильно?
[*]Есть ли предложения по отладке ответа 401, учитывая, что он работал при использовании Curl?
< /ul>
Заранее спасибо.
Изменить: я исправил строку, которая устанавливает токен носителя, но я все еще получаю то же самое исключение 401.

Подробнее здесь: https://stackoverflow.com/questions/785 ... httpclient
Ответить

Быстрый ответ

Изменение регистра текста: 
Смайлики
:) :( :oops: :roll: :wink: :muza: :clever: :sorry: :angel: :read: *x)
Ещё смайлики…
   
К этому ответу прикреплено по крайней мере одно вложение.

Если вы не хотите добавлять вложения, оставьте поля пустыми.

Максимально разрешённый размер вложения: 15 МБ.

Вернуться в «C#»