IHttpResponse Redirect Issue inside Response Filters

968 views
Skip to first unread message

Ras

unread,
Jul 8, 2012, 11:30:58 PM7/8/12
to servic...@googlegroups.com
Hi
I have following scenario where I use facebook authentication in my application, even though we request email address, if user modifies the query string and manually remove scope parameter, and continue, ServiceStack will input null value to Email field of UserOAuthProvider table, in order to avoid this I have add database constraint on that field, ServiceStack FaceBookAuthProvider throws SqlException as expected, since it is built in ServiceStack service interface I couldn't find a way to handle it, so I used ResponseFilter to check this error, 

        public void ExceptionResponseFilter(Container container, IHttpRequest req, IHttpResponse res, object dto)
        {
            var error = dto as IHttpError;
            if (error == null) return;

So now i can clearly see the exception comes in dto variable, now I need to redirect the user to different error page, so i tried calling

res.Redirect()  (also RedirecToUrl) in both cases I get following error (instead of redirecting), so how do we properly redirect user to different page (url) inside ResponseFilter


This page contains the following errors:

error on line 4 at column 1: Extra content at the end of the document

Below is a rendering of the page up to the first error.

 Object moved Object moved to here.



Thanks

Demis Bellot

unread,
Jul 9, 2012, 1:32:52 AM7/9/12
to servic...@googlegroups.com
Can you show the full source code that's generating the response and an example of the HTTP output?

It sounds like the browser is expecting the wrong Content-Type.


Ras

unread,
Jul 9, 2012, 2:12:39 AM7/9/12
to servic...@googlegroups.com

Hi Demis,

Actually this was required to avoid getting ServiceStack white error screen when some exception throws FacebookAuthProvider code, (Also this can be tested with SocialBootstrapApi by adding not null constraint to the Email column - if you need more information i can simply modify SocialBootstrapApi project and send over)



First I register this Response Filter AppHost.Configure

ResponseFilters.Add((req, res, dto) => responseFilters.ExceptionResponseFilter(container, req, res, dto));   


Here I just check for error condition and redirect to different url (i tried local addresses and now it is set to "google.com"

        public void ExceptionResponseFilter(Container container, IHttpRequest req, IHttpResponse res, object dto)
        {
            var error = dto as IHttpError;
            if (error == null) return;

            res.Redirect("www.google.com");
        }


This is the HTML (and I can see it append the given URL into /api/auth - what i want is simply direct user to different location - error page)

----------------
<html><head><title>Object moved</title></head><body>
<h2>Object moved to <a href="/api/auth/www.google.com">here</a>.</h2>
</body></html>
<AuthResponse xmlns:i="http://www.w3.org/2001/XMLSchema-instance" xmlns="">
<ResponseStatus>
<ErrorCode>ThreadAbortException</ErrorCode>
<Message>Thread was being aborted.</Message>
<StackTrace> at System.Threading.Thread.AbortInternal()
at System.Threading.Thread.Abort(Object stateInfo)
at System.Web.HttpResponse.AbortCurrentThread()
at System.Web.HttpResponse.End()
at System.Web.HttpResponse.Redirect(String url, Boolean endResponse, Boolean permanent)
at System.Web.HttpResponse.Redirect(String url)
at ServiceStack.WebHost.Endpoints.Extensions.HttpResponseWrapper.Redirect(String url)
at sf.Host.App_Start.ResponseFilters.ExceptionResponseFilter(Container container, IHttpRequest req, IHttpResponse res, Object dto) in C:\gitwip\sf\src\sf.Host\App_Start\ResponseFilters.cs:line 97
at sf.Host.App_Start.AppHost.&amp;lt;&amp;gt;c__DisplayClass17.&amp;lt;RegisterSfResponseFilters&amp;gt;b__16(IHttpRequest req, IHttpResponse res, Object dto) in C:\gitwip\sf\src\sf.Host\App_Start\AppHost.cs:line 139
at ServiceStack.WebHost.Endpoints.EndpointHost.ApplyResponseFilters(IHttpRequest httpReq, IHttpResponse httpRes, Object response)
at ServiceStack.WebHost.Endpoints.RestHandler.ProcessRequest(IHttpRequest httpReq, IHttpResponse httpRes, String operationName)</StackTrace>
</ResponseStatus>
</AuthResponse>



These is the HEADER information at that time
---------------
  1. Request URL:
  2. Request Method:
    GET
  3. Status Code:
    500 Internal Server Error
  4. Request Headersview source
    1. Accept:
      text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
    2. Accept-Charset:
      ISO-8859-1,utf-8;q=0.7,*;q=0.3
    3. Accept-Encoding:
      gzip,deflate,sdch
    4. Accept-Language:
      en-GB,en-US;q=0.8,en;q=0.6
    5. Cache-Control:
      max-age=0
    6. Connection:
      keep-alive
    7. Cookie:
      hblid=DP5VWXYISMZYISHH8P0VF5Q613228106; olfsk=olfsk7464345195330679; ss-pid=c+r0bKOooUiPiY0RZximTQ==; ss-id=RyxGQeoxKku09zvig0V5aw==; ss-opt=perm; X-UAId=1
    8. Host:
      localhost:88
    9. User-Agent:
      Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/536.11 (KHTML, like Gecko) Chrome/20.0.1132.47 Safari/536.11
  5. Query String Parametersview URL encoded
    1. code:
      AQDQfVethb1A6jVG9T1DY6vtsJA1jTdK8qt_bXKcAAYMtZOVnnh2U1VLc5swEoHRskJQFMtSrrv9uTrnA5iQGXv_Es6C98CQVm63MnqoUi4YWh0v0lAuuIcehJZSqntIbIMAbnWqjVLHvGiAcXKq-pl5QrrYOxJFS2ZPW8DNfdR0wDmylDA1rMGLp2a4IZWCTMg
  6. URL fragment
    1. #:
      _=_
  7. Response Headersview source
    1. Cache-Control:
      private
    2. Content-Length:
      1494
    3. Content-Type:
      application/xml; charset=utf-8
    4. Date:
      Mon, 09 Jul 2012 06:01:25 GMT
    5. Location:
      /api/auth/www.google.com
    6. Server:
      Microsoft-IIS/8.0
    7. X-AspNet-Version:
      4.0.30319
    8. X-MiniProfiler-Ids:
      ["71693b507e6646439e04c6d1a612e71e","d9758c4cc20941caa13a41bc0f5ae7ed","0cffcfa7df184c6a9f6eca922136c170","ccb2ac9c6612487cb166b422b376451e","a379d3ebf78e42daae187dad903127a4","1b7f8d20e53744bc9dfc99736abcba0c"]
    9. X-Powered-By:
    10. X-SourceFiles:
      =?UTF-8?B?QzpcZ2l0d2lwXHNmXHNyY1xzZi5Ib3N0XGFwaVxhdXRoXGZhY2Vib29r?=

Demis Bellot

unread,
Jul 9, 2012, 3:58:54 AM7/9/12
to servic...@googlegroups.com
You need to close the Response when you want to end the request and stop further processing of the request.
Try adding after your redirect:

response.Close(); 


Ras

unread,
Jul 9, 2012, 5:08:51 AM7/9/12
to servic...@googlegroups.com
Hi Demis

and I again change my code as follows and tried


      public void ExceptionResponseFilter(Container container, IHttpRequest req, IHttpResponse res, object dto)
        {
            var error = dto as IHttpError;
            if (error == null) return;

            
            res.Redirect("www.google.com");
            res.Close();
            
        } 

But i still getting same error, 

so in order to make it easier you to recreate i downloaded fresh copy of SocialBootsrapApi project and did following changes..

1. In the UserAuth.mdf I set Email field of UserOAuthProvider table to not null so it will throw an exception when we try to insert null
2. In web.config I removed email field from following config
<add key="oauth.facebook.Permissions" value="read_stream,offline_access" />
So that we will not get the email (actual scenario is user can manually remove scope from URL when he was asked for permission)

3. I added following ResponseFilter to AppHost.Configure to redirect, 


            ResponseFilters.Add((req, res, dto) =>
            {
                var error = dto as ServiceStack.ServiceHost.IHttpError;
                if (error == null) return;
                res.Redirect("http://www.google.com");
                res.Close();
            });


When you run this app and when you allow app from facebook and return back it will throw and sqlexception and then move to response filter, finally it shows the error I mentioned above

Thanks


ServiceStack-SocialBootstrapApi-Redirect.zip

Demis Bellot

unread,
Jul 10, 2012, 3:48:03 AM7/10/12
to servic...@googlegroups.com
Hi,

Note: the project didn't throw an error for me because Facebook continued to return my email for some reason.
Anyway an easier way to throw an exception in the AuthService is to just provide your own validation function (which gets called on every request):

    AuthService.ValidateFn = (service, method, dto) => { throw new Exception("Always throws"); };

With the exception now getting thrown, for some reason: 

res.Redirect(".."); 

Which calls ASP.NET underlying HttpResponse.Redirect, Throws a ThreadAbort exception when attempting to redirect after an exception has been thrown.

But you can use the res.RedirectToUrl() extension method which just adds a Location header and terminates the request. 
The modified source code looks like:

    res.StatusCode = (int)HttpStatusCode.Redirect;
    res.RedirectToUrl("http://www.google.com");
    res.Close();

Cheers,

Demis Bellot

unread,
Jul 10, 2012, 3:55:31 AM7/10/12
to servic...@googlegroups.com
Actually the  RedirectToUrl() extension method already ends the request so you don't need to res.Close() it. So the full source code changes looks like: 

using ServiceStack.ServiceHost;
... 

AuthService.ValidateFn = (service, method, dto) => { throw new Exception("Always throws"); };
 
ResponseFilters.Add((req, res, dto) => {

    var error = dto as IHttpError;
    if (error == null) return;
    res.StatusCode = (int)HttpStatusCode.Redirect;
    res.RedirectToUrl("http://www.google.com");
});

Ras

unread,
Jul 10, 2012, 6:31:51 AM7/10/12
to servic...@googlegroups.com
Thanks a lot Demis, it is working now and following line actually makes it work
   res.StatusCode = (int)HttpStatusCode.Redirect;

probably you may insert that line into RedirectToUrl() method itself

Thank you and appreciate your time and effort

Demis Bellot

unread,
Jul 10, 2012, 12:48:24 PM7/10/12
to servic...@googlegroups.com
Yeah wasn't added cause there are different redirect codes, so I've added an overrideable status code instead :)
Reply all
Reply to author
Forward
0 new messages