rewriter throws errors when calling web service

May 7, 2009 at 5:37 PM
I'm setting up url-rewriter for a major change to my blog. My main interest is to change post titles used by the blog engine that I've been using for a few years (Community Server) into a format appropriate for the site's new backend, BlogEngine.

That's working fine. Thanks.

But I run into a problem when I try to call a web service with the rewrite engine turned on. If I call the web service's WebMethod using the HTTP GET verb, I get a 404 "File does not exist" exception on the call. (The same service works fine with 'RewriteEngine off').

Using a webmethod with Rewriter appears to require some kind of special rule for the service, but multiple searches haven't yet revealed to me what the extra step is. I'm guessing the rule would have something to do with {REQUEST_METHOD}, but I'm not a skilled mod_rewrite jockey and can't figure out what to do with it. I'd appreciate suggestions.

(BTW, I'm running Ajax on the site. As I said, it all works fine without Rewriter, but breaks when Rewriter is turned on

This is the exception that's thrown with when the service method is called with the GET verb:
[HttpException]: File does not exist.
   at System.Web.StaticFileHandler.GetFileInfo(String virtualPathWithPathInfo, String physicalPath, HttpResponse
 response)
   at System.Web.StaticFileHandler.ProcessRequestInternal(HttpContext context)
   at System.Web.DefaultHttpHandler.BeginProcessRequest(HttpContext context, AsyncCallback callback,
 Object state)
   at System.Web.HttpApplication.CallHandlerExecutionStep.System.Web.HttpApplication.IExecutionStep.Execute
()
If I call the service using the default POST verb, the exception thrown is even more problematic.

This is the exception thrown when POST is used for the webmethod:

[HttpException]: The HTTP verb POST used to access path 'ServiceName.asmx/MethodName' is not allowed
   at System.Web.DefaultHttpHandler.BeginProcessRequest(HttpContext context, AsyncCallback callback, 
 Object state)
   at System.Web.HttpApplication.CallHandlerExecutionStep.System.Web.HttpApplication.IExecutionStep.Execute
()
   at System.Web.HttpApplication.ExecuteStep(IExecutionStep step, Boolean& completedSynchronously)

Clearly, there's something special that I need to do in the Rewriter rules to allow the webservice calls to execute.

Suggestions?
May 7, 2009 at 6:27 PM
And a bit of extra information:

The 'Requested URL' when making a call to the service is done in (what I believe is called) REST format, like so: TheService.asmx/TheMethod.

There is a file called 'TheService.asmx', but there isn't one called 'TheMethod'.

I don't know if that format is contributing to the problem, but I figured it was worth mentioning.
Coordinator
May 7, 2009 at 7:33 PM
Is that the full exception, or is there more.  The only reason I ask is because there is not enough there for me to trouble shoot the problem with you. Can you add

RewriteLog c:\path\to\my\blog\log.txt
RewriteLogLevel 9

this should go right under RewriteEngine On

Let me know the output.
May 7, 2009 at 10:29 PM
Thanks for the quick reply.

The message I posted earlier is the error message I get when I examine the web response in Firebug (the request/js debugger for Firefox).

When I turn on the log, this is all I get about the request:

Rule Processing: RewriteLogLevel: 9
Rule Processing: Comment: #
Rule Processing: Comment: # Place Rules Below
Rule Processing: Comment: #
Rule Processing: RewriteBase: /
Rule Processing: Comment: # forward webService requests without further processing
Rule Processing: RewriteRule: (.*)\.asmx $1\.asmx [L]
Rule Processing: Comment: # deny access to evil robots site rippers offline browsers and other nasty scum
Rule Processing: RewriteCond: %{HTTP_USER_AGENT} ^Anarchie [OR]
...

**********************************************************************************
Rewrite: Input: http://localhost/TickerService.asmx/GetTickerList
Rule 0: Input: /TickerService.asmx/GetTickerList
Rule 0: Rule Pattern Matched
Rule 0: Output: /TickerService\.asmx/GetTickerList
Rewrite: Output: http://localhost/TickerService/.asmx/GetTickerList
**********************************************************************************
There's more, about processing of other rules (this is mostly a much-commented out version of your example rules). But
that's the only response in the log that relates to the web service.

I threw that rule about "asmx" in there to see if it might help, but the error happens with or without that rule.

The exception is thrown when RewriteEngine=on, but not when RewriteEngine=off.

A bit more info:
This is what the request header looks like wit RewriteEngine=off:

Server Microsoft-IIS/5.1
 Date  Thu, 07 May 2009 22:07:41 GMT
 X-Powered-By ASP.NET
 Set-Cookie ...
 Cache-Control  private, max-age=0
 
 Content-Type application/json; charset=utf-8
 Content-Length  31887
 Host localhost
 User-Agent  Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.9.0.10) Gecko/2009042316 Firefox/3.0.10 (.NET CLR
 3.5.30729)
 Accept  text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
 Accept-Language  en-us
 Accept-Encoding  gzip,deflate
 Accept-Charset  ISO-8859-1,utf-8;q=0.7,*;q=0.7
 Keep-Alive  300
 Connection  keep-alive
 Content-Type application/json; charset=utf-8
 Referer http://localhost/ 
 Cookie ...
Everything remains the same in the request header with RewriteEngine=on
Server	

Microsoft-IIS/5.1
Date	Thu, 07 May 2009 22:21:08 GMT
X-Powered-By	ASP.NET
Cache-Control	private
Content-Type	text/html; charset=utf-8
Content-Length	2310

Request Headers
Host	localhost
User-Agent	Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.9.0.10) Gecko/2009042316 Firefox/3.0.10 (.NET CLR
 3.5.30729)
Accept	text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language	en-us
Accept-Encoding	gzip,deflate
Accept-Charset	ISO-8859-1,utf-8;q=0.7,*;q=0.7
Keep-Alive	300
Connection	keep-alive
Content-Type	application/json; charset=utf-8
Referer	http://localhost/
Cookie	...
May 7, 2009 at 10:35 PM
I'm making a vastly simplified version of the page and service so that you can see what's happening.
May 8, 2009 at 5:04 PM

I guess I should have tried the simplified version before asking the question.

The webservice is working when I run the simplified test app through the WebDev server. (My main test site is running on localhost through IIS 5.)

I haven't figured out where the issue was coming from, but I'm working on it.

 

<%@ Page Language="C#" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">


<html xmlns="http://www.w3.org/1999/xhtml">
<head>
    <title>Untitled Page</title>
    
</head>
<body>
    <form id="form1" runat="server">
        <asp:ScriptManager ID="ScriptManager1" runat="server" >
            <Services>
                <asp:ServiceReference Path="~/SimpleService.asmx" />
            </Services>
        </asp:ScriptManager>
        
        <div>Test rewrites</div>

                <div id="progressDiv" style="float: left; display: block">
                    <span class="ticker-time">  Loading blog headlines...</span>
                </div>
                <div style="float:right"><input id="updateButton" type="button" onclick="getTickerList('tickerlistDiv', 'updateButton')" value="  Update  " />  </div>
                <br />
                <div id="tickerlistDiv"></div>     <script type="text/javascript">
    function pageLoad()
    {
        getTickerList("tickerlistDiv", "updateButton");
    }

    function getTickerList(elemToUpdate, buttonElem)
    {
        var userContext = [elemToUpdate, buttonElem];
        TestService.HelloService.HelloWorld(onGetTickerSuccess, onGetTickerFailure, userContext);
        $get(buttonElem).value = "Retrieving";
        $get("progressDiv").style.display = "block";

    }
    function onGetTickerSuccess(result, context, methodName)
    {
        var elemToUpdate = context[0];
        var buttonElem = context[1];
        $get(elemToUpdate).innerHTML = result;
        $get(buttonElem).value = "  Update  ";
        $get("progressDiv").style.display = "none";
    }
    function onGetTickerFailure(error, context, methodName)
    {
        var elemToUpdate = context[0];
        var buttonElem = context[1];
        $get(elemToUpdate).innerHTML = error.get_message();
        $get(buttonElem).value = "  Update  ";
        $get("progressDiv").style.display = "none";
    }
</script>
    

<!--               -
  SERVICE  -->
<!--WebService Language="C#" Class="TestService.HelloService"-->

using System;
using System.Web;
using System.Web.Services;
using System.Web.Services.Protocols;
using System.Web.Script.Services;

namespace TestService
{
    [ScriptService]
    [WebService(Namespace = "http://seaqwa.com/")]
    [WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]

    public class HelloService : System.Web.Services.WebService
    {

        [ScriptMethod(UseHttpGet = true)]
        [WebMethod]
        public string HelloWorld()
        {
            return "Hello World";
        }

    }
}

<!-- TEST RULES  -->

RewriteEngine on
RewriteLog c:\inetpub\testservice\rewriterlog.txt
RewriteLogLevel 9

#
# Place Rules Below
#

RewriteBase /

# forward webService requests without further processing
#RewriteCond {HTTP_HOST} localhost:1314 [NC]
RewriteRule (.*\.asmx.*) $1 [NC, L]

(That rule isn't strictly necessary, but I put it in to be able to see what's happening in the log and to stop processing for the .asmx file.

Coordinator
May 8, 2009 at 8:20 PM

I may have found the problem.  See if you get any different results when you use this rule:

RewriteRule (.*)\.asmx $1.asmx [L]

intead of

RewriteRule (.*)\.asmx $1\.asmx [L]

You don't escape the output strings, so your \. was causing an issue.  When rewriting for the server.  I will give your example a try, but let me know if this works.

May 8, 2009 at 9:16 PM

Doh!

That's it. Thanks very much for taking time with this false alarm.

The rule I ended up using is this:

RewriteRule (.*\.asmx.*) $1 [NC, L]

That seems to guarantee that service requests pass through quickly.

Looking at the log, I see that I should do the same for .axd requests, since -- between the two of them -- BlogEngine and Ajax add a whole bunch of handlers.

Again... Sorry for the false alarm, and thanks very much for this great program. It will be invaluable for my new site which must rewrite most SE hits for the next few months.

I also suspect that it will be able to replace some of the internal rewriting that BE is doing and handle it faster, since I can customize Url-Rewriter to use only the db that my site is using and don't need the multiple-storage options that are available in BE, and must be handled by its internal routines.

Coordinator
May 11, 2009 at 3:35 AM

No problem.  I would love to see more BlogEngine.NET people using the rewriter.  Also there is an application configuration part, where some base configuration can be done in code, so if the BlogEngine devs ever want to give up on their current custom rewriting I would be happy to show them how to intigrate in some compiled rules.  But given their stance on external assemblies I am guessing they would never go for it.  :)  Well good luck and if you have any more questions, please feel free to post again.