Made MessagingUtilities.CreateQueryString exception more useful.
[dotnetoauth.git] / src / DotNetOpenAuth / Messaging / StandardWebRequestHandler.cs
blob47a5040292d7a0ae80f9d634fdb4cf7c44c47478
1 //-----------------------------------------------------------------------
2 // <copyright file="StandardWebRequestHandler.cs" company="Andrew Arnott">
3 // Copyright (c) Andrew Arnott. All rights reserved.
4 // </copyright>
5 //-----------------------------------------------------------------------
7 namespace DotNetOpenAuth.Messaging {
8 using System;
9 using System.IO;
10 using System.Net;
11 using System.Net.Sockets;
12 using System.Reflection;
13 using DotNetOpenAuth.Messaging;
15 /// <summary>
16 /// The default handler for transmitting <see cref="HttpWebRequest"/> instances
17 /// and returning the responses.
18 /// </summary>
19 internal class StandardWebRequestHandler : IDirectWebRequestHandler {
20 /// <summary>
21 /// The value to use for the User-Agent HTTP header.
22 /// </summary>
23 private static string userAgentValue = Assembly.GetExecutingAssembly().GetName().Name + "/" + Assembly.GetExecutingAssembly().GetName().Version;
25 #region IWebRequestHandler Members
27 /// <summary>
28 /// Prepares an <see cref="HttpWebRequest"/> that contains an POST entity for sending the entity.
29 /// </summary>
30 /// <param name="request">The <see cref="HttpWebRequest"/> that should contain the entity.</param>
31 /// <returns>
32 /// The writer the caller should write out the entity data to.
33 /// </returns>
34 /// <exception cref="ProtocolException">Thrown for any network error.</exception>
35 /// <remarks>
36 /// <para>The caller should have set the <see cref="HttpWebRequest.ContentLength"/>
37 /// and any other appropriate properties <i>before</i> calling this method.</para>
38 /// <para>Implementations should catch <see cref="WebException"/> and wrap it in a
39 /// <see cref="ProtocolException"/> to abstract away the transport and provide
40 /// a single exception type for hosts to catch.</para>
41 /// </remarks>
42 public TextWriter GetRequestStream(HttpWebRequest request) {
43 ErrorUtilities.VerifyArgumentNotNull(request, "request");
45 return GetRequestStreamCore(request);
48 /// <summary>
49 /// Processes an <see cref="HttpWebRequest"/> and converts the
50 /// <see cref="HttpWebResponse"/> to a <see cref="DirectWebResponse"/> instance.
51 /// </summary>
52 /// <param name="request">The <see cref="HttpWebRequest"/> to handle.</param>
53 /// <returns>
54 /// An instance of <see cref="DirectWebResponse"/> describing the response.
55 /// </returns>
56 /// <exception cref="ProtocolException">Thrown for any network error.</exception>
57 /// <remarks>
58 /// Implementations should catch <see cref="WebException"/> and wrap it in a
59 /// <see cref="ProtocolException"/> to abstract away the transport and provide
60 /// a single exception type for hosts to catch.
61 /// </remarks>
62 public DirectWebResponse GetResponse(HttpWebRequest request) {
63 ErrorUtilities.VerifyArgumentNotNull(request, "request");
65 try {
66 Logger.DebugFormat("HTTP {0} {1}", request.Method, request.RequestUri);
67 using (HttpWebResponse response = (HttpWebResponse)request.GetResponse()) {
68 return new DirectWebResponse(request.RequestUri, response);
70 } catch (WebException ex) {
71 if (Logger.IsErrorEnabled) {
72 if (ex.Response != null) {
73 using (var reader = new StreamReader(ex.Response.GetResponseStream())) {
74 Logger.ErrorFormat("WebException from {0}: {1}", ex.Response.ResponseUri, reader.ReadToEnd());
76 } else {
77 Logger.ErrorFormat("WebException {1} from {0}, no response available.", request.RequestUri, ex.Status);
81 throw ErrorUtilities.Wrap(ex, MessagingStrings.ErrorInRequestReplyMessage);
85 #endregion
87 /// <summary>
88 /// Initiates a POST request and prepares for sending data.
89 /// </summary>
90 /// <param name="request">The HTTP request with information about the remote party to contact.</param>
91 /// <returns>The stream where the POST entity can be written.</returns>
92 private static TextWriter GetRequestStreamCore(HttpWebRequest request) {
93 // Some sites, such as Technorati, return 403 Forbidden on identity
94 // pages unless a User-Agent header is included.
95 if (string.IsNullOrEmpty(request.UserAgent)) {
96 request.UserAgent = userAgentValue;
99 try {
100 return new StreamWriter(request.GetRequestStream());
101 } catch (SocketException ex) {
102 throw ErrorUtilities.Wrap(ex, MessagingStrings.WebRequestFailed, request.RequestUri);
103 } catch (WebException ex) {
104 using (HttpWebResponse response = (HttpWebResponse)ex.Response) {
105 if (response != null && response.StatusCode == HttpStatusCode.ExpectationFailed &&
106 request.ServicePoint.Expect100Continue) {
107 // Some OpenID servers doesn't understand the Expect header and send 417 error back.
108 // If this server just failed from that, we're trying again without sending the
109 // "Expect: 100-Continue" HTTP header. (see Google Code Issue 72)
110 // We don't just set Expect100Continue = !avoidSendingExpect100Continue
111 // so that future requests don't reset this and have to try twice as well.
112 // We don't want to blindly set all ServicePoints to not use the Expect header
113 // as that would be a security hole allowing any visitor to a web site change
114 // the web site's global behavior when calling that host.
115 request = request.Clone();
116 request.ServicePoint.Expect100Continue = false; // TODO: investigate that CAS may throw here, and we can use request.Expect instead.
117 // request.Expect = ""; // alternative to ServicePoint if we don't have permission to set that, but be sure to change the if clause above if we use this.
118 return GetRequestStreamCore(request);
119 } else {
120 throw ErrorUtilities.Wrap(ex, MessagingStrings.WebRequestFailed, request.RequestUri);