joaotavora / snooze

Common Lisp RESTful web development
207 stars 22 forks source link

URI questions #15

Open atgreen opened 5 years ago

atgreen commented 5 years ago

I was hoping to have URIs like...

http://localhost/api/v1/hello http://localhost/api/v1/test

But I see that this doesn't work: (defroute api/v1/hello ...

I don't see any mention of how method names map to paths. How would I do something like this?

Thanks!

joaotavora commented 5 years ago

I don't see any mention of how method names map to paths.

Heh, I'm quite surprised: the README goes into this at length, it's the main function in Snooze!!

I think you have to decide first what your REST resource is. Is it "API"? If so your route should be

`(defroute api (:get :text/html version string) ...)

And then version and string will be bound to the uninterned symbols #v1 and #hello respectively, in the first case, and #v1 and #test in the second case. There are ample ways to configure this (see the README).

If however, you want two rest resources: "api/v1/hello" and "api/v1/test", then you should write two routes defining two resources.

(defroute api/v1/hello (:get :text/html &rest arguments-if-you-want-them) ...)

and

(defroute api/v1/test (:get :text/html &rest arguments-if-you-want-them) ...)

atgreen commented 5 years ago

When I do this...

(snooze:defroute api/v1/start (:get "text/plain") (setf player-count (+ 1 player-count)) (format nil "~A" player-count))

I get a 404 error. Hunchentoot reports this:

127.0.0.1 - [2018-11-20 08:38:39] "GET /api/v1/start/ HTTP/1.1" 404 13 "-" "Go-http-client/1.1"

joaotavora commented 5 years ago

Indeed snooze seems to have trouble when the resource name has "/" in it. This seems like a bug, I will take a look at it.

joaotavora commented 5 years ago

This works for you, right?

(snooze:defroute foo (:get "text/plain") "bar")

And then accessing http://localhost:<yourport>/foo right?

joaotavora commented 5 years ago

It's not a bug, it's like this for design. Snooze provides a customization point for your case, snooze:*resource-name-function*. Here is its doc, which you can find in the api.lisp file:

(defparameter *resource-name-function* 'default-resource-name
  "How to search for resource names in URI paths.

Value is a function designator called on every request with the
request's URI path. The function might be called with the empty
string.

This function should return two values: a resource designator (a
string, symbol or a resource) and a relative URI string stripped of the
resource-designating part.  If the first value returned is nil,
*HOME-RESOURCE* is used to lookup a suitable resource.

The function should *not* attempt any URI-decoding of the component
string. That is done automatically elsewhere.

The default value is DEFAULT-RESOURCE-NAME, which returns the first
path component as the first value and the remaining URI as the second
value..

Can be let-bound to modify the URI scheme used by a particular
server.")

So you need to write a suitable function for something that will split the string up to three "/" in the string and split it there, returning both parts as separate values.

atgreen commented 5 years ago

Yes, that works.

On Tue., Nov. 20, 2018, 10:29 a.m. João Távora <notifications@github.com wrote:

This works for you, right?

(snooze:defroute foo (:get "text/plain") "bar")

And then accessing http://localhost:/foo right?

— You are receiving this because you authored the thread. Reply to this email directly, view it on GitHub https://github.com/joaotavora/snooze/issues/15#issuecomment-440313842, or mute the thread https://github.com/notifications/unsubscribe-auth/AAFfieaB8gl_rV5GWZ9ic35wuy7j-LLxks5uxB_EgaJpZM4Yp8R9 .

atgreen commented 5 years ago

Thanks.. I'll have a look later today!

On Tue., Nov. 20, 2018, 10:31 a.m. João Távora <notifications@github.com wrote:

It's not a bug, it's like this for design. Snooze provides a customization point for your case, snooze:resource-name-function. Here is its doc, which you can find in the api.lisp file:

— You are receiving this because you authored the thread. Reply to this email directly, view it on GitHub https://github.com/joaotavora/snooze/issues/15#issuecomment-440314723, or mute the thread https://github.com/notifications/unsubscribe-auth/AAFfiT06hlS6TCHRNLOqah4Upnuz10xjks5uxCBKgaJpZM4Yp8R9 .

mdbergmann commented 4 years ago

I'm also having some trouble here. I didn't want to create a new ticket.

I hope I didn't overlook something but I've read the readme a few times now.

Basically I have two routes:

(defroute blog (:get :text/html)              ;; index
(defroute blog (:get :text/html name)    ;; named resource

When compiling this I get an error on the second definition:

Lambda list of method #<STANDARD-METHOD BLOG (SNOOZE-VERBS:GET
                                              SNOOZE-TYPES:TEXT/HTML)> 
is incompatible with that of the generic function BLOG.
Method's lambda-list : (SNOOZE-VERBS:HTTP-VERB SNOOZE-TYPES:TYPE)
Generic-function's   : (SNOOZE-VERBS:HTTP-VERB SNOOZE-TYPES:TYPE
                        NAME)

In principle it's clear, two methods with the same name. But my lacking CL knowledge doesn't let me overcome this. Any ideas?

joaotavora commented 4 years ago

When you use defroute you are basically writing defmethod. Just expand it (in sly it's C-c C-m on the ( of the defroute. In fact, you can defmethod directly.

Now, this should be enough to hint that a route in Snooze is only a method in a generic function. So when you do the first blog you establish a certain protocol for the generic function, when you try to change that protocol, Lisp complains. Most Lisps (I think SBCL at least) give you the option to replace the protocol of the generic function with the new one.

You should learn about "Generic Functions". In Snooze, a REST resource is represented by a generic function. The methods of the generic functions are the different routes that provide access to that resource. Different types of access. Read access is a GET specializer on the first HTTP-VERB arg, write access if PUT or POST or DELETE on that arg. And the same with the content types.

Also, in your case, it seems like you want a /blog/<name-of-blog> route. Ask yourself: is <name-of-blog> mandatory, or is it optional? If it's optional it should be an &optional or &key argument. If it's mandatory, then pointing your browser to just /blog automatically generates a 400 error, as one would expect.

mdbergmann commented 4 years ago

or is it optional? If it's optional it should be an &optional

Great. This was a good pointer. Got it working with &optional.

And then version and string will be bound to the uninterned symbols #v1 and #hello respectively, in the first case, and #v1 and #test in the second case. There are ample ways to configure this (see the README).

Are you refering to the uri-to-arguments method?

mdbergmann commented 4 years ago

I believe you can close this one as well.

joaotavora commented 4 years ago

Are you refering to the uri-to-arguments method?

Yes, and its counterpart arguments-to-uri. Whatever you do to uri-to-arguments you should also do to arguments-to-uri, so that you can later use blog-path to generate perfectly matching URIs for your blog REST resource.

You could also use read-for-resource and write-for-resource, if you're concerned with the string representation of all the arguments for a particular resource. Most times, you will want uri-to-arguments and arguments-to-uri though, leveraging their default implementations with call-next-method, of course.