IIS Buffering Requests Content Before Controller Action - vb.net

I am trying to setup an "external" web api that will be able to receive in large http posts (+1GB) and forward the stream to another "internal" web api that writes the request contents to a file. I have model my implementation based on examples of using a custom WebHostBufferPolicySelector and using the UseBufferedInputStream method in the controller method. It works as expected when using IIS Express, no significate increase in memory footprint, but as soon as my code is deployed to IIS, the memory footprint is substantial and results in OOM.
I have put tracing statements in my controller methods and in my WebHostBufferPolicySelector.UseBufferedInputStream, and have verified that UseBufferedInputStream always is returning false and that my controller methods are getting hit. The only difference that I noticed is that when I debug, the time stamps between UseBufferedInputStream and my controller method are very close. Where hosted on IIS, the time stamps are very far apart, suggesting that something in between when UseBufferedInputStream is called and my controller method is called is buffering up the request entirely.
I am looking for some tips on to find out what that is causing the request to get buffered and how for it not to buffer and using streaming all the way.
Client is coming at the external web api with a content type of application/octet-stream with Transfer Encoding of Chucked.
Used to build out implementation
https://forums.asp.net/t/2018289.aspx?Web+API2+WebHostBufferPolicySelector+UseBufferedInputStream+override
https://www.strathweb.com/2012/09/dealing-with-large-files-in-asp-net-web-api/
Proxy Web Api Controller Method
<HttpPost, Route("postLargeFile")>
Protected Overridable Async Function PostLargeFile() As Threading.Tasks.Task(Of IHttpActionResult)
Configuration.Services.GetTraceWriter.Info(Request, $"{Me.GetType.Namespace}.{NameOf(MyProxyController)}", "Started {0}", NameOf(MyProxyController.PostLargeFile))
Dim internalHttpClient As HttpClient
Dim fowardingContent As StreamContent = Nothing
Dim fowardingMessage As HttpRequestMessage = Nothing
Dim fowardingResponse As HttpResponseMessage = Nothing
Dim externalResponse As HttpResponseMessage = Nothing
Try
internalHttpClient = New HttpClient()
internalHttpClient.BaseAddress = "https://myinternalService.com"
fowardingMessage = New HttpRequestMessage(HttpMethod.Post, "https://myinternalService.com/saveLargeFile")
fowardingContent = New StreamContent(HttpContext.Current.Request.GetBufferlessInputStream(True))
CopyContentHeaders(Request.Content, fowardingContent)
fowardingMessage.Headers.TransferEncodingChunked = True
fowardingMessage.Content = fowardingContent
fowardingResponse = Await internalHttpClient.SendAsync(fowardingMessage, HttpCompletionOption.ResponseHeadersRead)
externalResponse = New HttpResponseMessage(fowardingResponse.StatusCode)
externalResponse.Content = New StreamContent(Await fowardingResponse.Content.ReadAsStreamAsync)
CopyContentHeaders(fowardingResponse.Content, externalResponse.Content)
Return New Results.ResponseMessageResult(externalResponse)
Catch ex As Exception
Return InternalServerError(ex)
Finally
Configuration.Services.GetTraceWriter.Info(Request, $"{Me.GetType.Namespace}.{NameOf(MyProxyController)}", "Finished {0}", NameOf(MyProxyController.PostLargeFile))
End Try
End Function
Internal Web Api Controller Method
<HttpPost, Route("saveLargeFile")>
Protected Overridable Async Function SaveLargeFile() As Threading.Tasks.Task(Of IHttpActionResult)
Configuration.Services.GetTraceWriter.Info(Request, $"{Me.GetType.Namespace}.{NameOf(MyInternalController)}", "Started {0}", NameOf(MyInternalController.PostLargeFile))
Dim bufferlessStream As IO.Stream
Dim fowardingContent As StreamContent = Nothing
Try
bufferlessStream = HttpContext.Current.Request.GetBufferlessInputStream()
Using fileStream As IO.FileStream = IO.File.Create("MyFile.txt")
bufferlessStream.CopyTo(fileStream)
fileStream.Flush()
End Using
Return New Results.StatusCodeResult(Net.HttpStatusCode.Created, Me)
Catch ex As Exception
Return InternalServerError(ex)
Finally
Configuration.Services.GetTraceWriter.Info(Request, $"{Me.GetType.Namespace}.{NameOf(MyInternalController)}", "Finished {0}", NameOf(MyInternalController.PostLargeFile))
End Try
End Function
Policy Selector Configuration
Public Class MyBufferPolicySelector
Inherits Http.WebHost.WebHostBufferPolicySelector
Public Property Tracer As ITraceWriter
Public Overrides Function UseBufferedInputStream(hostContext As Object) As Boolean
UseBufferedInputStream = False
Tracer?.Info(Nothing, $"{Me.GetType.Namespace}.{NameOf(MyBufferPolicySelector)}", "{0} UseBufferedInputStream={1}", HttpContext.Current?.Request?.Url?.AbsoluteUri, UseBufferedInputStream)
Return UseBufferedInputStream
End Function
End Class
WebApiConfig for both Internal and External Web APIs
Public Module WebApiConfig
Public Sub Register(ByVal config As HttpConfiguration)
Dim tracer As SystemDiagnosticsTraceWriter
' Web API configuration and services
' Web API routes
config.MapHttpAttributeRoutes()
tracer = config.EnableSystemDiagnosticsTracing
tracer.IsVerbose = True
tracer.MinimumLevel = Tracing.TraceLevel.Debug
GlobalConfiguration.Configuration.Services.Replace(GetType(IHostBufferPolicySelector), New MyBufferPolicySelector() With {.Tracer = tracer})
End Sub
End Module

I was able to figure out what was causing the the buffering in IIS. The below link lead me to the uploadReadAheadSize setting in IIS. This was maxed out. So this would cause IIS to fully read in/buffer in the request before passing it into the module where the web api pipeline exists (web api controllers). After setting it to the default, I saw my large file posts not get buffered, the app pool memory footprint remained low, no more out of memory exceptions, and a large performance boost. Great!
But now I have the same issue as described in the below link. When SSL is required, set in IIS, which it is required in our non development environments, the uploadReadAheadSize needs to be increased so the ssl can work in the ssl module I guess. It might have to do with some SSL renegotiation.
Can anybody describe a way to prevent the buffering in SSL to keep the memory footprint low and prevent out of memory exceptions for large http posts?
Large file upload when using ssl and client certificates (uploadReadAheadSize) but dont want all data to be readahead

Related

Receiving a 404 response using swagger with VB.Net

I am trying to hook up Swagger to a VB.Net REST API project. No matter what I try the app continues to return a 404 response when hitting the swagger/ui/index URL. Initially, on start-up I enter the following URL: http://localhost/ApiNameHere/Swagger.
IIS attempts to access (redirects to) http://localhost/swagger/ui/index. This returns a 404 response.
Swashbuckle was used to install Swagger
Locally, the app runs in IIS.
I have seen recommendations to delete the .vs folder. I have tried that, to no avail.
Here is the SwaggerConfig.vb file:
Public Class SwaggerConfig
Public Shared Sub Register(config As HttpConfiguration)
Dim thisAssembly = GetType(SwaggerConfig).Assembly
config.EnableSwagger(
Sub(c)
c.SingleApiVersion("v1", "ApiNameHere")
End Sub
).EnableSwaggerUi()
End Sub
End Class
Here is the relevant Startup.vb code :
Dim config = New HttpConfiguration()
config.MapHttpAttributeRoutes()
config.Routes.MapHttpRoute("DefaultApi", "api/{controller}/{id}", New With {RouteParameter.Optional})
SwaggerConfig.Register(config)
app.UseWebApi(config)
Any thoughts?

WCF rest client: unable to send HTTP message when triggered by another WCF service in the solution

I'm building a solution meant to consume a REST service via a WCF proxy client, retrieve XML data and insert it into SQL tables.
My solution has 4 projects:
one console app for hosting everything
one WCF client class for connecting to the REST service and deserializing XML data into objects
one class for parsing deserialized objects and writing to SQL tables.
one WCF service class, hosted in the console, that implements one operation which raises en event.
What works fine:
instantiating my WCF client class and using it from the console directly,
instantiating my WCF client class and using it when manually triggering my WCF service event
What does not work:
instantiating my WCF client class and using it when my WCF service raises its event via HTTP method call => Error raised: Unexpected end of file
I looked at verbose trace logs, and it says that sending the HTTP message failed.
Any clue of where this can come from?
This is my WCF service implementation:
<ServiceBehavior(InstanceContextMode:=ServiceModel.InstanceContextMode.Single _
, IncludeExceptionDetailInFaults:=True)> _
Public Class RemoteService
Implements IRemoteService
Public Event getGroups As MyHostEventHandler
Public Sub DoWork() Implements IRemoteService.DoWork
RaiseEvent getGroups()
End Sub
End Class
Public Delegate Sub MyHostEventHandler()
Here is now the code I have for my console application:
Sub getGroups()
' Instantiate WCF client
Dim proxy As EE2014_DataSolution.EERestAPI = New EE2014_DataSolution.EERestAPI()
' Call WCF REST method getGroups2014()
Dim response_groups As groups = proxy.getGroups2014()
' Instantiate SQL writer class
Dim sql As SqlDataWriter.SqlDataWriter = New SqlDataWriter.SqlDataWriter()
' Pass deserialized object to SQL writter class
Dim numRowsWritten As Integer = sql.WriteGroups(response_groups)
Console.WriteLine(numRowsWritten & " rows updated")
End Sub
Sub Main()
' Instantiate WCF service
Dim host As ServiceHost = New ServiceHost(New RemoteService.RemoteService())
' Handle event (THIS WILL FAIL)
AddHandler CType(host.SingletonInstance, RemoteService.RemoteService).getGroups, AddressOf getGroups
' Start WCF service
host.Open()
Console.WriteLine("RemoteService started at " & Now)
' Wait for WCF messages
Console.WriteLine("Press any key to send HTTP request manually.")
Console.ReadLine()
' Get groups directly from console (THIS WILL WORK)
getGroups()
' Wait for WCF messages
Console.WriteLine("Press any key to send HTTP request manually via event.")
Console.ReadLine()
' Raise the WCF service event manually (THIS WILL WORK)
CType(host.SingletonInstance, RemoteService.RemoteService).DoWork()
' Exit
Console.WriteLine("Press any key to exit.")
Console.ReadLine()
End Sub
I found the origin of my problem: the context of the "client" WCF method is overridden by the context of my WCF service. Indeed, thanks to diagnostics configuration, I noticed that my application tried to send POST messages instead of GET messages, which led me to the following link, where lies the solution:
http://social.msdn.microsoft.com/Forums/en-US/wcf/thread/03a2b109-c400-49d4-891e-03871ae0d083/#416d8cbc-b855-46aa-8a6d-5d6b09db97b6

Apache Http Client Put Request Error

I'm trying to upload a file using the Apache Http Client's PUT method. The code is as below;
def putFile(resource: String, file: File): (Int, String) = {
val httpClient = new DefaultHttpClient(connManager)
httpClient.getCredentialsProvider.setCredentials(AuthScope.ANY, new UsernamePasswordCredentials(un, pw))
val url = address + "/" + resource
val put = new HttpPut(url)
put.setEntity(new FileEntity(file, "application/xml"))
executeHttp(httpClient, put) match {
case Success(answer) => (answer.getStatusLine.getStatusCode, "Successfully uploaded file")
case Failure(e) => {
e.printStackTrace()
(-1, e.getMessage)
}
}
}
When I tried running the method, I get to see the following error:
org.apache.http.NoHttpResponseException: The target server failed to respond
at org.apache.http.impl.conn.DefaultResponseParser.parseHead(DefaultResponseParser.java:101)
at org.apache.http.impl.io.AbstractMessageParser.parse(AbstractMessageParser.java:252)
at org.apache.http.impl.AbstractHttpClientConnection.receiveResponseHeader(AbstractHttpClientConnection.java:281)
at org.apache.http.impl.conn.DefaultClientConnection.receiveResponseHeader(DefaultClientConnection.java:247)
at org.apache.http.impl.conn.AbstractClientConnAdapter.receiveResponseHeader(AbstractClientConnAdapter.java:219)
at org.apache.http.protocol.HttpRequestExecutor.doReceiveResponse(HttpRequestExecutor.java:298)
at org.apache.http.protocol.HttpRequestExecutor.execute(HttpRequestExecutor.java:125)
at org.apache.http.impl.client.DefaultRequestDirector.tryExecute(DefaultRequestDirector.java:633)
at org.apache.http.impl.client.DefaultRequestDirector.execute(DefaultRequestDirector.java:454)
at org.apache.http.impl.client.AbstractHttpClient.execute(AbstractHttpClient.java:820)
I do not know what has gone wrong? I'm able to do GET requests, but PUT seems not to work! Any clues as to where I should look for?
Look on the server. If GET Works, but PUT does not, then you have to figure out the receiving end.
Also, you may want to write a simple HTML File that has a form with PUT Method in it to rule out your Java Part.
As a sidenode: Its technically possible that something in between stops the request from going through or the response reaching you. Best setup a dummy HTTP Server to do the testing against.
Maybe its also a timeout issue, so the server takes to long to process your PUT.
The connection you are trying to use is a stale connection and therefore the request is failing.
But why are you only seeing an error for the PUT request and you are not seeing it for the GET request?
If you check the DefaultHttpRequestRetryHandler class you will see that by default HttpClient attempts to automatically recover from I/O exceptions. The default auto-recovery mechanism is limited to just a few exceptions that are known to be safe.
HttpClient will make no attempt to recover from any logical or HTTP protocol errors (those derived from HttpException class).
HttpClient will automatically retry those methods that are assumed to be idempotent. Your GET request, but not your PUT request!!
HttpClient will automatically retry those methods that fail with a transport exception while the HTTP request is still being transmitted to the target server (i.e. the request has not been fully transmitted to the server).
This is why you don't notice any error with your GET request, because the retry mechanism handles it.
You should define a CustomHttpRequestRetryHandler extending the DefaultHttpRequestRetryHandler. Something like this:
public class CustomHttpRequestRetryHandler extends DefaultHttpRequestRetryHandler {
#Override
public boolean retryRequest(IOException exception, int executionCount, HttpContext context) {
if(exception instanceof NoHttpResponseException) {
return true;
}
return super.retryRequest(exception, executionCount, context);
}
}
Then just assign your CustomHttpRequestRetryHandler
final HttpClientBuilder httpClientBuilder = HttpClients.custom();
httpClientBuilder.setRetryHandler(new CustomHttpRequestRetryHandler());
And that's it, now your PUT request is handled by your new RetryHandler (like the GET was by the default one)

How do I _properly_ serve an XAP file from a self-hosted WCF service?

I have myself a self-hosted WCF server setup, which serves a clientaccesspolicy.xml and an index.htm which is just points to my xap (accessible via app.xap).
I'm currently serving them via the following code:
Public Function GetPolicy() As System.IO.Stream Implements IClientAccessPolicy.GetPolicy
WebOperationContext.Current.OutgoingResponse.ContentType = "application/xml"
Return New IO.MemoryStream(IO.File.ReadAllBytes("Server Files/ClientAccessPolicy.xml"))
End Function
Public Function GetIndex() As IO.Stream Implements ISilverlightServer.GetIndex
WebOperationContext.Current.OutgoingResponse.ContentType = "text/html"
Return New IO.MemoryStream(IO.File.ReadAllBytes("Server Files/index.htm"))
End Function
Public Function GetXap() As IO.Stream Implements ISilverlightServer.GetXap
WebOperationContext.Current.OutgoingResponse.ContentType = "application/x-silverlight-app"
Return New IO.MemoryStream(IO.File.ReadAllBytes("Server Files/app.xap"))
End Function
It works, does the job I'm after. However, I don't think this streams the xap properly, and I know it's a streaming type. If this isn't streaming it properly, how should I stream it?
(The endpoint that the XAP and index.htm files are coming through has a webHttpBinding binding)
Is it being streamed properly? Or should I make some changes?
That is fine, the XAP file doesn't need to be streamed to the client. Actually, it needs to be first fully downloaded (thus buffered at the client) for the SL application to start, so you don't need to worry about streaming in this case.

Reporting Services Authentication issue

I am trying to programmatically render a PDF using Azure Reporting Services. I suspect that the actual PDF retrieval is fine, but I cannot find a way to authenticate the connection before requesting the report (via URL). I am working in the services layer of my web application and I cannot use a web reference (might not work with Azure) and it doesn't make sense to use a ReportViewer control (since it's a service layer method).
I have all the details to connect, but I suspect that I require a cookie to authenticate and I'm not sure how to manually create this. Any suggestions/solutions?
Here's my code so far:
string userName = BJConfigurationManager.GetSetting("ReportingServiceUsername");
string password = BJConfigurationManager.GetSetting("ReportingServicePassword");
NetworkCredential networkCredential = new NetworkCredential(userName, password);
Domain.Report report = GetReportById(id);
int timeout = 30; //seconds
string url = "https://bleh.ctp.reporting.database.windows.net/ReportServer/Pages/ReportViewer.aspx?...";
string destinationFileName = "#C:\\Temp.pdf";
// Create a web request to the URL
HttpWebRequest MyRequest = (HttpWebRequest)WebRequest.Create(url);
MyRequest.PreAuthenticate = true;
MyRequest.Credentials = networkCredential;
MyRequest.Timeout = timeout * 1000;
try
{
// Get the web response -- THE RESPONSE COMES BACK AS UNAUTHENTICATED...
HttpWebResponse MyResponse = (HttpWebResponse)MyRequest.GetResponse();
Check out the section titled "SOAP Management Endpoint Programmatic Access":
http://msdn.microsoft.com/en-us/library/windowsazure/771e88b6-ab0f-4910-a5fa-5facd8d56767#SOAPManagement.
It explains how to authenticate using a cookie container without a ReportViewer control.
I don't think that is going to work. Azure Reporting uses Forms Authentication and as I understand it, you aren't going to be able to match the Forms Auth cookie along with the MachineKey for encryption.
I was trying to accomplish the same task..but using a WebRequest was impossible.
I changed the approach using a ServerReport class like this:
ServerReport report;
report = new ServerReport();
report.ReportServerUrl = new Uri(reportServerName + "/ReportServer");
report.ReportPath = "/ReportPath";
report.ReportServerCredentials = new ReportServerCredentials();
report.SetParameters(new Microsoft.Reporting.WebForms.ReportParameter("param1", param1));
report.SetParameters(new Microsoft.Reporting.WebForms.ReportParameter("param2", param1));
return report.Render(reportParams.OutputFormat);
The ReportServerCredentials class must implement the IReportServerCredentials interface like this.
More info about the IReportServerCredentials interface and implementation here.