-
Notifications
You must be signed in to change notification settings - Fork 4.7k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add StringContent ctor providing tighter control over charset #17036
Comments
Looks like it will always put something out https://github.com/dotnet/corefx/blob/d0dc5fc099946adc1035b34a8b1f6042eddb0c75/src/System.Net.Http/src/System/Net/Http/StringContent.cs#L29 |
The You can always replace the |
It's certainly invalid. RFC 2046 (RFC 6657 is also relevant) allows for the charset parameter as part of content-types only if it's |
@benaadams @JonHanna Thanks for finding the references to both. Even if it were valid (e.g., I don't know if this is what is choking my ATS REST service calls, but it's possible, since @davidsh How? I get an exception if I try to mod the header by adding ... client.DefaultRequestHeaders.Add("Content-Type", contentType); ... throws ...
Here's the hacked code I'm playing with (i.e., not for production ... I'm just hacking around to get the requests working). How would I set the using (var client = new HttpClient())
{
DateTime requestDT = DateTime.UtcNow;
UTF8Encoding utf8Encoding = new UTF8Encoding();
string storageAccountName = "<STORAGE_ACCOUNT_NAME>";
string tableName = "<TABLE_NAME>";
//string requestMethod = "POST";
string dateInRfc1123Format = requestDT.ToString("R", CultureInfo.InvariantCulture);
string storageServiceVersion = "2015-04-05";
string storageAccountKey = "<STORAGE_ACCOUNT_KEY>";
string canonicalizedResource = $"/{storageAccountName}/{tableName}";
string contentType = "application/atom+xml";
// Content
string requestPayload = GetRequestContentInsertXml(requestDT, "PKEY", "RKEY");
var stringContent = new StringContent(requestPayload, Encoding.UTF8, contentType);
// Will not authorize against the service:
// string stringToSign = $"{requestMethod}\n\n{contentType}\n{dateInRfc1123Format}\n{canonicalizedResource}";
// SharedKeyLite works:
string stringToSign = $"{dateInRfc1123Format}\n{canonicalizedResource}";
string authorizationHeader = CreateAuthorizationParameter(stringToSign, storageAccountName, storageAccountKey);
AuthenticationHeaderValue myAuthHeaderValue = new AuthenticationHeaderValue("SharedKeyLite", authorizationHeader);
// Headers
client.DefaultRequestHeaders.Authorization = myAuthHeaderValue;
client.DefaultRequestHeaders.Add("x-ms-date", dateInRfc1123Format);
client.DefaultRequestHeaders.Add("x-ms-version", storageServiceVersion);
client.DefaultRequestHeaders.Add("DataServiceVersion", "3.0;NetFx");
client.DefaultRequestHeaders.Add("MaxDataServiceVersion", "3.0;NetFx");
// Doesn't work: client.DefaultRequestHeaders.Add("Content-Type", contentType);
var responseMessage = client.PostAsync($"https://{storageAccountName}.table.core.windows.net/{tableName}", stringContent).Result;
responseResult.Append("StatusCode: " + responseMessage.StatusCode.ToString() + " ");
} |
When modifying "known" headers, you need to use the properties and not the 'DefaultRequestHeaders' method. For request entity-body headers (content), you need to modify the `HttpRequestMessage.Content.Headers1 collection. You are getting an exception telling you that basically. So, you need code like this: var content = new StringContent(...);
content.Headers.ContentType = ...; You can also use methods like this as well: var content = new StringContent(...);
content.Headers.Remove(...);
content.Headers.Add(...); |
@davidsh Ah ... ok ... thx ... that got the header squared away ... stringContent.Headers.ContentType = new MediaTypeHeaderValue(contentType); And 🎉 YES! 🎉 ... that cleared up the problem with ATS REST API: My issue is solved, but do you want to leave this open given what @JonHanna reported? |
We will research this to determine if there are RFC issues. This code has existed for 5+ years and was previously reviewed by our RFC compliance experts. I'll leave this open for now. |
I was incorrect. While |
My concern about it remains: If the dev sets a value explicitly on In any case where the header is part of a service-generated hash that will be checked against a supplied signature (without the If the dev sets an explicit value, seems like it should be honored. At least mod the description to note that |
Just for completeness, I checked the Azure Service REST Service using the default ... StringContent("<data>", Encoding.UTF8, "application/atom+xml"); ... but supplying the content type to the signature string as ... string contentType = "application/atom+xml; charset=utf-8";
string stringToSign =
$"{requestMethod}\n\n{contentType}\n{dateInRfc1123Format}\n{canonicalizedResource}"; That works. It's still awfully cryptic tho without a little help. |
@JonHanna Usually it is the media type registration document that defines what parameters are allowed. In the application/atom+xml the registration refers explicitly back RFC3023 for the rules, as you stated. Interestingly, the .net stack loves adding charset onto
It would be awesome if this could be fixed. |
We plan to fix this in CoreFx. We need to study if porting this to .NET Framework (Desktop) will have any app-compat issues. |
This will require a new overload to the StringContent constructor so that the charset isn't specified by default on the |
Hi var content = "{\"PartitionKey\":\"MemberSyncDto_4-2018\",\"RowKey\":\"1218\",\"SType\":\"3\"}"; // ,\n
var updateContent = new StringContent(content, Encoding.UTF8, "application/json");
updateContent.Headers.ContentType = new MediaTypeHeaderValue("application/json");
using (var client = new HttpClient())
{
// Add the request headers for x-ms-date and x-ms-version.
DateTime now = DateTime.UtcNow;
var dateHeader = now.ToString("R", CultureInfo.InvariantCulture);
client.DefaultRequestHeaders.Authorization = AzureStorageAuthenticationHelper.GetAuthorization(
storageAccountName, storageAccountKey, now, new Uri(uri));
client.DefaultRequestHeaders.Add("x-ms-date", dateHeader);
client.DefaultRequestHeaders.Add("x-ms-version", "2018-03-28");
client.DefaultRequestHeaders.Add("DataServiceVersion", "3.0;NetFx");
client.DefaultRequestHeaders.Add("MaxDataServiceVersion", "3.0;NetFx");
Console.WriteLine("x-ms-date: {0}", dateHeader);
Console.WriteLine("Content: {0}", content);
Console.WriteLine("Auth: {0}", client.DefaultRequestHeaders.Authorization);
Console.WriteLine("ContentLength: {0}", content.Length);
var responseMessage = await client.PostAsync(uri, updateContent, cancellationToken);
Console.WriteLine("Result: {0}", responseMessage.StatusCode);
}
} Quite simpel..... If I do this in Postman it works, but i keep getting 415 error "Unsupported Media Type" :-( Why? Desperate now, tried everything..... Can do GET requests, but not POST. I can get the same error in Postman by removing the Content-Type header... |
Here is my request in Fiddler:
And here the response:
Here is the one from Postman that works, what am I missing?
|
Did the trick :-) |
@davidsh, can you elaborate on why this would require a new API? |
This is the current API: public partial class StringContent : System.Net.Http.ByteArrayContent
{
public StringContent(string content) : base (default(byte[])) { }
public StringContent(string content, System.Text.Encoding encoding) : base (default(byte[])) { }
public StringContent(string content, System.Text.Encoding encoding, string mediaType) : base (default(byte[])) { }
} None of the overloads take any 'charset' parameter. But they all create one anyways. See public StringContent(string content, Encoding encoding, string mediaType)
: base(GetContentByteArray(content, encoding))
{
// Initialize the 'Content-Type' header with information provided by parameters.
MediaTypeHeaderValue headerValue = new MediaTypeHeaderValue((mediaType == null) ? DefaultMediaType : mediaType);
headerValue.CharSet = (encoding == null) ? HttpContent.DefaultStringEncoding.WebName : encoding.WebName;
Headers.ContentType = headerValue;
} So, we would need a new overload that could explicitly take a charset parameter (or null) etc. |
Triage: We need to finish the API proposal. |
@karelz What if a new overload used a MediaTypeHeaderValue as the parameter? That would seem like the simplest solution. Am I missing something? |
That sounds good to me: public StringContent(string content, MediaTypeHeaderValue mediaType) In addition to being simple, it has the added benefit that it lets you avoid a MediaTypeHeaderValue allocation per StringContent, if you want to just use the same instance over and over. |
We believe the correct API is this: public StringContent(string content, MediaTypeHeaderValue mediaType) |
I would like to create a PR for this. After the API review stephentoub mentioned the problem with the "missing" encoding. public StringContent(string content, MediaTypeHeaderValue mediaType) Or should it be (as proposed by ericsampson ): public StringContent(string content, System.Text.Encoding encoding, string mediaType, string charset) ..To avoid parsing the public StringContent(string content, Encoding encoding, MediaTypeHeaderValue mediaType) |
I'm fine with both of the APIs proposed in #17036 (comment), but this needs to go back to API review before we can accept a PR for it. I would also look at: class MediaTypeHeaderValue
{
public MediaTypeHeaderValue(string mediaType, string charSet);
} |
Triage: We would like to know details about the user scenarios to make sure we understand which APIs we should add and why. @DaveSenn the above will block PR for now, sorry ... let's first clarify what we want to add properly. Thanks for understanding. |
@karelz sure np, thanks for the info. |
Want to be abe to add a
Currenty unusable for this purpose
Performance concern with the proposed api that was previously accepted as it introduces requirement for secondary parsing #17036 (comment) |
I think the current proposal is the following: public class StringContent
{
public StringContent(string content);
+ public StringContent(string content, MediaTypeHeaderValue mediaType);
public StringContent(string content, Encoding encoding);
public StringContent(string content, Encoding encoding);
public StringContent(string content, Encoding encoding, string mediaType);
+ public StringContent(string content, Encoding encoding, MediaTypeHeaderValue mediaType);
} Is that correct? Any further suggestions/concerns? @scalablecory also suggested the following, which seems like something we should do as well (or at least get approved to do in the future): public class MediaTypeHeaderValue
{
public MediaTypeHeaderValue(string mediaType)
+ public MediaTypeHeaderValue(string mediaType, string charSet);
} |
Sounds good, it would definitely be nice to get the new constructor at the same time if possible :) |
I marked this as 6.0 so it will get reviewed in the near future. That doesn't mean this is committed for 6.0. |
Looks good as proposed. We even discussed the casing on "charSet", and decided it's correct since it matches the existing property. namespace System.Net.Http.Headers
{
public class StringContent
{
public StringContent(string content);
+ public StringContent(string content, MediaTypeHeaderValue mediaType); // Already approved in iteration 1
public StringContent(string content, Encoding encoding);
public StringContent(string content, Encoding encoding);
public StringContent(string content, Encoding encoding, string mediaType);
+ public StringContent(string content, Encoding encoding, MediaTypeHeaderValue mediaType); // NEW API proposal
}
public class MediaTypeHeaderValue
{
public MediaTypeHeaderValue(string mediaType)
+ public MediaTypeHeaderValue(string mediaType, string charSet); // NEW API proposal
}
} |
Is there anyone in this issue interested in offering a PR? |
I am currently coding up an attempt at creating the new APIs. In addition, I will try a PR after done. |
Implemented by #63231 |
API Approval - iteration 2
This is being submitted again for approval because we decided additional APIs were needed.
Copied from proposal in #17036 (comment)
Previous API approval is here: #17036 (comment)
Original post
Consider the following code ...
According to Wireshark, I'm seeing a POST using
HttpClient
with this ...Shouldn't that
Content-Type
value just be ...?? Confused, because something is choking my Azure Table Storage requests with ...
... and since
SharedKeyLite
, which only requires the date and resource, works with mydateInRfc1123Format
andcanonicalizedResource
, I've sort of narrowed it down to thecontentType
of the request (shown above) not matching what I'm putting into the signature sting, namely justapplication/atom+xml
.The text was updated successfully, but these errors were encountered: