From 44e9849ed1190dbc29ffa97fa5df286dc9794edb Mon Sep 17 00:00:00 2001 From: "Justin Clark-Casey (justincc)" Date: Thu, 11 Jul 2013 23:02:30 +0100 Subject: [PATCH] Fix regression where llHTTPRequests which did not get an OK response returned 499 and the exception message in the http_response event rather than the actual response code and body. This was a regression since commit 831e4c3 (Thu Apr 4 00:36:15 2013) This commit also adds a regression test for this case, though this currently only works with Mono This aims to address http://opensimulator.org/mantis/view.php?id=6704 --- OpenSim/Framework/Util.cs | 7 +- .../HttpRequest/ScriptsHttpRequests.cs | 77 ++++--- .../Tests/ScriptsHttpRequestsTests.cs | 201 ++++++++++++++++++ prebuild.xml | 1 + 4 files changed, 245 insertions(+), 41 deletions(-) create mode 100644 OpenSim/Region/CoreModules/Scripting/HttpRequest/Tests/ScriptsHttpRequestsTests.cs diff --git a/OpenSim/Framework/Util.cs b/OpenSim/Framework/Util.cs index ba6cc75e12..cafe103135 100644 --- a/OpenSim/Framework/Util.cs +++ b/OpenSim/Framework/Util.cs @@ -141,6 +141,11 @@ namespace OpenSim.Framework public static FireAndForgetMethod DefaultFireAndForgetMethod = FireAndForgetMethod.SmartThreadPool; public static FireAndForgetMethod FireAndForgetMethod = DefaultFireAndForgetMethod; + public static bool IsPlatformMono + { + get { return Type.GetType("Mono.Runtime") != null; } + } + /// /// Gets the name of the directory where the current running executable /// is located @@ -1326,7 +1331,7 @@ namespace OpenSim.Framework ru = "OSX/Mono"; else { - if (Type.GetType("Mono.Runtime") != null) + if (IsPlatformMono) ru = "Win/Mono"; else ru = "Win/.NET"; diff --git a/OpenSim/Region/CoreModules/Scripting/HttpRequest/ScriptsHttpRequests.cs b/OpenSim/Region/CoreModules/Scripting/HttpRequest/ScriptsHttpRequests.cs index 6793fc8226..1a62405078 100644 --- a/OpenSim/Region/CoreModules/Scripting/HttpRequest/ScriptsHttpRequests.cs +++ b/OpenSim/Region/CoreModules/Scripting/HttpRequest/ScriptsHttpRequests.cs @@ -419,7 +419,7 @@ namespace OpenSim.Region.CoreModules.Scripting.HttpRequest get { return _reqID; } set { _reqID = value; } } - public HttpWebRequest Request; + public WebRequest Request; public string ResponseBody; public List ResponseMetadata; public Dictionary ResponseHeaders; @@ -431,18 +431,11 @@ namespace OpenSim.Region.CoreModules.Scripting.HttpRequest SendRequest(); } - /* - * TODO: More work on the response codes. Right now - * returning 200 for success or 499 for exception - */ - public void SendRequest() { - HttpWebResponse response = null; - try { - Request = (HttpWebRequest) WebRequest.Create(Url); + Request = WebRequest.Create(Url); Request.Method = HttpMethod; Request.ContentType = HttpMIMEType; @@ -480,14 +473,17 @@ namespace OpenSim.Region.CoreModules.Scripting.HttpRequest } } - foreach (KeyValuePair entry in ResponseHeaders) - if (entry.Key.ToLower().Equals("user-agent")) - Request.UserAgent = entry.Value; - else - Request.Headers[entry.Key] = entry.Value; + if (ResponseHeaders != null) + { + foreach (KeyValuePair entry in ResponseHeaders) + if (entry.Key.ToLower().Equals("user-agent") && Request is HttpWebRequest) + ((HttpWebRequest)Request).UserAgent = entry.Value; + else + Request.Headers[entry.Key] = entry.Value; + } // Encode outbound data - if (OutboundBody.Length > 0) + if (OutboundBody != null && OutboundBody.Length > 0) { byte[] data = Util.UTF8.GetBytes(OutboundBody); @@ -510,12 +506,19 @@ namespace OpenSim.Region.CoreModules.Scripting.HttpRequest { throw; } - response = (HttpWebResponse)e.Response; + + HttpWebResponse response = (HttpWebResponse)e.Response; + + Status = (int)response.StatusCode; + ResponseBody = response.StatusDescription; _finished = true; } } catch (Exception e) { +// m_log.Debug( +// string.Format("[SCRIPTS HTTP REQUESTS]: Exception on request to {0} for {1} ", Url, ItemID), e); + Status = (int)OSHttpStatusCode.ClientErrorJoker; ResponseBody = e.Message; _finished = true; @@ -528,33 +531,27 @@ namespace OpenSim.Region.CoreModules.Scripting.HttpRequest try { - response = (HttpWebResponse)Request.EndGetResponse(ar); + try + { + response = (HttpWebResponse)Request.EndGetResponse(ar); + } + catch (WebException e) + { + if (e.Status != WebExceptionStatus.ProtocolError) + { + throw; + } + + response = (HttpWebResponse)e.Response; + } + Status = (int)response.StatusCode; - Stream resStream = response.GetResponseStream(); - StringBuilder sb = new StringBuilder(); - byte[] buf = new byte[8192]; - string tempString = null; - int count = 0; - - do + using (Stream stream = response.GetResponseStream()) { - // fill the buffer with data - count = resStream.Read(buf, 0, buf.Length); - - // make sure we read some data - if (count != 0) - { - // translate from bytes to ASCII text - tempString = Util.UTF8.GetString(buf, 0, count); - - // continue building the string - sb.Append(tempString); - } + StreamReader reader = new StreamReader(stream, Encoding.UTF8); + ResponseBody = reader.ReadToEnd(); } - while (count > 0); // any more data to read? - - ResponseBody = sb.ToString(); } catch (Exception e) { @@ -587,4 +584,4 @@ namespace OpenSim.Region.CoreModules.Scripting.HttpRequest Request.Abort(); } } -} +} \ No newline at end of file diff --git a/OpenSim/Region/CoreModules/Scripting/HttpRequest/Tests/ScriptsHttpRequestsTests.cs b/OpenSim/Region/CoreModules/Scripting/HttpRequest/Tests/ScriptsHttpRequestsTests.cs new file mode 100644 index 0000000000..f638f9161b --- /dev/null +++ b/OpenSim/Region/CoreModules/Scripting/HttpRequest/Tests/ScriptsHttpRequestsTests.cs @@ -0,0 +1,201 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSimulator Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using System; +using System.Collections.Generic; +using System.IO; +using System.Net; +using System.Reflection; +using System.Runtime.Serialization; +using System.Text; +using System.Threading; +using log4net.Config; +using NUnit.Framework; +using OpenMetaverse; +using OpenMetaverse.Assets; +using OpenSim.Framework; +using OpenSim.Region.CoreModules.Scripting.HttpRequest; +using OpenSim.Region.Framework.Scenes; +using OpenSim.Tests.Common; +using OpenSim.Tests.Common.Mock; + +namespace OpenSim.Region.CoreModules.Scripting.HttpRequest.Tests +{ + class TestWebRequestCreate : IWebRequestCreate + { + public TestWebRequest NextRequest { get; set; } + + public WebRequest Create(Uri uri) + { +// NextRequest.RequestUri = uri; + + return NextRequest; + +// return new TestWebRequest(new SerializationInfo(typeof(TestWebRequest), new FormatterConverter()), new StreamingContext()); + } + } + + class TestWebRequest : WebRequest + { + public override string ContentType { get; set; } + public override string Method { get; set; } + + public Func OnEndGetResponse { get; set; } + + public TestWebRequest() : base() + { +// Console.WriteLine("created"); + } + +// public TestWebRequest(SerializationInfo serializationInfo, StreamingContext streamingContext) +// : base(serializationInfo, streamingContext) +// { +// Console.WriteLine("created"); +// } + + public override IAsyncResult BeginGetResponse(AsyncCallback callback, object state) + { +// Console.WriteLine("bish"); + TestAsyncResult tasr = new TestAsyncResult(); + callback(tasr); + + return tasr; + } + + public override WebResponse EndGetResponse(IAsyncResult asyncResult) + { +// Console.WriteLine("bosh"); + return OnEndGetResponse(asyncResult); + } + } + + class TestHttpWebResponse : HttpWebResponse + { + public string Response { get; set; } + + public TestHttpWebResponse(SerializationInfo serializationInfo, StreamingContext streamingContext) + : base(serializationInfo, streamingContext) {} + + public override Stream GetResponseStream() + { + return new MemoryStream(Encoding.UTF8.GetBytes(Response)); + } + } + + class TestAsyncResult : IAsyncResult + { + WaitHandle m_wh = new ManualResetEvent(true); + + object IAsyncResult.AsyncState + { + get { + throw new System.NotImplementedException (); + } + } + + WaitHandle IAsyncResult.AsyncWaitHandle + { + get { return m_wh; } + } + + bool IAsyncResult.CompletedSynchronously + { + get { return false; } + } + + bool IAsyncResult.IsCompleted + { + get { return true; } + } + } + + /// + /// Test script http request code. + /// + /// + /// This class uses some very hacky workarounds in order to mock HttpWebResponse which are Mono dependent (though + /// alternative code can be written to make this work for Windows). However, the value of being able to + /// regression test this kind of code is very high. + /// + [TestFixture] + public class ScriptsHttpRequestsTests : OpenSimTestCase + { + /// + /// Test what happens when we get a 404 response from a call. + /// + [Test] + public void Test404Response() + { + TestHelpers.InMethod(); + TestHelpers.EnableLogging(); + + if (!Util.IsPlatformMono) + Assert.Ignore("Ignoring test since can only currently run on Mono"); + + string rawResponse = "boom"; + + TestWebRequestCreate twrc = new TestWebRequestCreate(); + + TestWebRequest twr = new TestWebRequest(); + //twr.OnEndGetResponse += ar => new TestHttpWebResponse(null, new StreamingContext()); + twr.OnEndGetResponse += ar => + { + SerializationInfo si = new SerializationInfo(typeof(HttpWebResponse), new FormatterConverter()); + StreamingContext sc = new StreamingContext(); +// WebHeaderCollection headers = new WebHeaderCollection(); +// si.AddValue("m_HttpResponseHeaders", headers); + si.AddValue("uri", new Uri("test://arrg")); +// si.AddValue("m_Certificate", null); + si.AddValue("version", HttpVersion.Version11); + si.AddValue("statusCode", HttpStatusCode.NotFound); + si.AddValue("contentLength", 0); + si.AddValue("method", "GET"); + si.AddValue("statusDescription", "Not Found"); + si.AddValue("contentType", null); + si.AddValue("cookieCollection", new CookieCollection()); + + TestHttpWebResponse thwr = new TestHttpWebResponse(si, sc); + thwr.Response = rawResponse; + + throw new WebException("no message", null, WebExceptionStatus.ProtocolError, thwr); + }; + + twrc.NextRequest = twr; + + WebRequest.RegisterPrefix("test", twrc); + HttpRequestClass hr = new HttpRequestClass(); + hr.Url = "test://something"; + hr.SendRequest(); + + while (!hr.Finished) + Thread.Sleep(100); + + Assert.That(hr.Status, Is.EqualTo((int)HttpStatusCode.NotFound)); + Assert.That(hr.ResponseBody, Is.EqualTo(rawResponse)); + } + } +} \ No newline at end of file diff --git a/prebuild.xml b/prebuild.xml index d9e32ea2c3..d32287a0d5 100644 --- a/prebuild.xml +++ b/prebuild.xml @@ -3102,6 +3102,7 @@ +