http://localhost:8080/streams/
Given that URL and the code, the in restish/app.py's locate_resources after
processing the PATH_INFO the segments variable contains [u'streams', u''].
That empty string entry seems to be keeping it from working. If causes the
loop calling resource_child to be called an extra time with a None result.
If I change the root.streams() method to return 'Streams(), []' (overriding
the u'' entry) it works.
So this seems like a bug to me but it is such a basic thing it seems like I
have to be doing something wrong. Hope this made sense. Here's the code.
---------------------------------------------------------------
from restish import http, resource
class Root(resource.Resource):
@resource.GET(accept='text')
def html(self, request):
return http.ok([('Content-Type', 'text/html')],
(w for w in ('hello', ' ', 'world')))
@resource.child()
def streams(self, request, segments):
return Streams()
class Streams(resource.Resource):
""" top level streams api """
@resource.GET()
def html(self, request):
return http.ok(
[('Content-Type', 'text/html')],
stream_all(request))
---------------------------------------------------------------
--
John Eikenberry
[j...@zhar.net - http://zhar.net]
[PGP public key @ http://zhar.net/jae_at_zhar_net.gpg]
______________________________________________________________
"Perfection is attained, not when no more can be added, but when no more
can be removed." -- Antoine de Saint-Exupery
You're right, it generates ("streams", "")
because:
/streams -> ("streams",)
What I usually do is to put an empty child, @resource.child("") that
redirects to itself.
Cheers,
--
Yoan
> -----BEGIN PGP SIGNATURE-----
> Version: GnuPG v1.4.9 (GNU/Linux)
>
> iEYEARECAAYFAkp6YMUACgkQMVBeMLjc14KxtgCg0OW7rn8uRMw89OjpmIhhfEeq
> 9bQAnjMizrtTNNjOIgksAk/yZK4KvpHz
> =DZp1
> -----END PGP SIGNATURE-----
>
>
Just to expand on Yoan's response a little ...
Strictly speaking, /streams and /streams/ are two distinct URLs and so
*may* be two distinct resources in an application. restish makes no
assumptions about how your application's resources are structured.
Yoan's solution is to make /streams the canonical URL by redirecting
/streams/ to /streams. The alternative, i.e. making /streams/ the
canonical URL, can be achieved with a simple mixin:
class SlashResourceMixin(object):
def resource_child(self, request, segments):
# Consume a single empty segment, returning the same resource.
if segments == ['']:
return self, []
return super(SlashResourceMixin, self).resource_child(
request, segments)
def __call__(self, request):
# If the url does not end with a slash then redirect to the same URL
# with the slash appended.
if request.url[-1] != '/':
return http.moved_permanently(request.url+'/')
return super(SlashResourceMixin, self).__call__(request)
class Streams(SlashResourceMixin, resource.Resource):
...
With that mixin any requests for /streams will be redirected (using a
301) to /streams/ and the resource will respond to that instead.
Of course, if you want this behaviour for every resource in your
application then simply define a common Resource base class that
includes the mixin and then derive your resources from your Resource
class.
Actually, this mixin (and tests) might be a good addition to restish
although it's unlikely to ever become the default. The mixin good
easily be extended a little to handle Yoan's use-case too. I'll add
that to my TODO list to look at.
Hope that helps.
- Matt
> Just to expand on Yoan's response a little ...
Both Yoan's and your fixes work and I understand what is going on now.
Thanks to you both.
> Strictly speaking, /streams and /streams/ are two distinct URLs and so
> *may* be two distinct resources in an application. restish makes no
> assumptions about how your application's resources are structured.
>
> Yoan's solution is to make /streams the canonical URL by redirecting
> /streams/ to /streams. The alternative, i.e. making /streams/ the
> canonical URL, can be achieved with a simple mixin:
I had thought of this and had tried the /stream URL. But it works as you
describe without an explicit 301. That is it is already sending the 301.
E.g.
$ curl -I http://localhost:8080/streams
HTTP/1.0 301 Moved Permanently
Server: PasteWSGIServer/0.5 Python/2.5.4
Date: Thu, 06 Aug 2009 17:03:00 GMT
location: http://localhost:8080/streams/
content-type: text/html
Connection: close
This is with just that simple code of mine running, not with your redirect
mixin. I'm running in a virtualenv with the latest pypi versions from
there and using paster as the server.
> --~--~---------~--~----~------------~-------~--~----~
> You received this message because you are subscribed to the Google Groups "ish.io" group.
> To post to this group, send email to is...@googlegroups.com
> To unsubscribe from this group, send email to ishio+un...@googlegroups.com
> For more options, visit this group at http://groups.google.co.uk/group/ishio?hl=en-GB
> -~----------~----~----~----~------~----~------~--~---
> I had thought of this and had tried the /stream URL. But it works as you
> describe without an explicit 301. That is it is already sending the 301.
> E.g.
>
> $ curl -I http://localhost:8080/streams
> HTTP/1.0 301 Moved Permanently
> Server: PasteWSGIServer/0.5 Python/2.5.4
> Date: Thu, 06 Aug 2009 17:03:00 GMT
> location: http://localhost:8080/streams/
> content-type: text/html
> Connection: close
>
> This is with just that simple code of mine running, not with your redirect
> mixin. I'm running in a virtualenv with the latest pypi versions from
> there and using paster as the server.
As an aside, my non-simplified (but still pretty simple) code has a third
layer to it so that you can have URLs like:
http://localhost:8080/streams/person
The code is simple, just another layer of child() and classes like those I
previously included.
The thing is at this level it doesn't do the 301 to the '/'d version of the
URL as it does on the /streams level. I'm not sure why, haven't had a
chance to dig in and see what is going on. Just relaying it for now.
Yeah, this is weird. If your code is not returning a 301 then it
should not be happening. Perhaps your browser has cached the 301 moved
*permanently* from when you were testing Yoan's or my suggestions?
Although that doesn't really make sense given the curl output you
posted earlier.
Could you post the code somewhere so we can take a look?
- Matt
It is returning a 301 for /streams, redirecting to /streams/. That is what
that curl output above is showing.
It is not redirecting for /streams/person.
I hadn't tested your solution. Just Yoan's and as you said, the curl output
precludes browser caching.
> Could you post the code somewhere so we can take a look?
Sure. I've attached the code. It is only slightly modified (all in one
file, simplified content), but has the same behaviour described above.
It has Yoan's workaround to the path/ issue I was having, but this doesn't
have any effect on the redirecting it is doing for /streams.
Thanks for all your help.
Doing a redirect is better so you have only one URL.
@resource.child('')
def company(self, request, segments):
return http.moved_permanently(request.url[:-1]) # or http.found
In theory you should use url.URL.parent to keep the querystring, but
as is it's not working perfectly.
btw Matt, can you take a look at this:
http://github.com/greut/restish/blob/7774e6db13828c381c35005e492d5745f2972cd5/restish/resource.py#L474
http://github.com/greut/restish/blob/7774e6db13828c381c35005e492d5745f2972cd5/restish/resource.py#L532
with that (and some other bits) : "curl -I" works, maybe not perfect
but still better than an 405 Method Not Allowed. The resource.HEAD
decorator isn't that useful because you should still send the expected
Content-Length according to the HTTP spec.
--
Yoan
2009/8/7 John Eikenberry <j...@zhar.net>:
> -----BEGIN PGP SIGNATURE-----
> Version: GnuPG v1.4.9 (GNU/Linux)
>
> iEYEARECAAYFAkp7nLoACgkQMVBeMLjc14JlDACfZkloHeh5Iqg4MVnhIzLdLCy9
> gMIAn3dRrfq2BHk4um2XkZJyc3IBoKhb
> =cJSq
> -----END PGP SIGNATURE-----
>
>
> $ curl http://localhost:8000/streams
Thanks for testing this (Matt as well). I'm still getting it, but I've
finally figured out what it causing it.
I simplified the code slightly that I sent in. One of the simplifications
was combining multiple files into one for easy of attaching. But it turns
out it was a sub-directory that was causing the problem. My resources/
directory looked like this...
resource/root.py
resource/streams/__init__.py
resource/streams/person.py
resource/streams/company.py
It turns out that resource/streams/ subdirectory was causing the redirect.
If you take the file I sent as the root.py and create a streams
subdirectory under resources (it can be empty), it will start redirecting.
$ curl http://127.0.0.1:8080/streams
stream all
$ mkdir resource/streams
$ curl http://127.0.0.1:8080/streams
<html>
<head><title>Moved Permanently</title></head>
<body>
<h1>Moved Permanently</h1>
<p>The resource has been moved to <a href="http://127.0.0.1:8080/streams/">http://127.0.0.1:8080/streams/</a>;
you should be redirected automatically.
The resource has moved to http://127.0.0.1:8080/streams/ - you should be redirected automatically.
<!-- --></p>
<hr noshade>
<div align="right">WSGI Server</div>
</body>
</html>
I don't remember reading about any directory based magic the docs. But it
definitely does seem to be doing something with that.
> Doing a redirect is better so you have only one URL.
>
> @resource.child('')
> def company(self, request, segments):
> return http.moved_permanently(request.url[:-1]) # or http.found
Yes, this is probably better.
> In theory you should use url.URL.parent to keep the querystring, but
> as is it's not working perfectly.
If you have time I'm curious about this. Is this documented somewhere, the
'should' part? Also what's wrong with it.
Thanks again.
Cheers,
--
Yoan
2009/8/8 John Eikenberry <j...@zhar.net>:
> -----BEGIN PGP SIGNATURE-----
> Version: GnuPG v1.4.9 (GNU/Linux)
>
> iEYEARECAAYFAkp8sPcACgkQMVBeMLjc14LKZgCfdskH4uaG7PvybDDOqRbk0MdL
> Js4An0qj9aArIghsSQKMT/BhsBQSQgmI
> =Qexo
> -----END PGP SIGNATURE-----
>
>
>
> It's maybe the fault of egg:Paste#static; as static files are served
> before your application. Can you invert them in [composite:main] and
> try again?
That got it. I had thought of it being paste related, but hadn't figured
out paste enough to nail it down. Thanks a lot.