-
Notifications
You must be signed in to change notification settings - Fork 2.7k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Resteasy - intercepting endpoint selection #25735
Comments
Just checking whether @FroMage has not already done something along these ends for Renarde. |
/cc @FroMage, @stuartwdouglas |
This is a very interesting feature IMO. |
I am also wondering whether it makes sense to do something Htmx specific, or provide the ability for user code to decide which Resource method should be executed... I'm personally leaning towards the former |
@geoand Quarkus shouldn't have anything specific to htmx. It should be generic feature. End user will decide how to use it |
I don't agree, for a couple reasons:
|
FYI this is how it was solved in the example we are creating for Quinoa, I guess the same would be working: and the template: |
And here is an ongoing discussion about Quarkus with Htmx (using NodeJS and Quinoa and Renarde) quarkiverse/quarkus-quinoa#113 |
Hi, So, this is very interesting. Lemme learn more about htmx, but meanwhile I can already say you could simplify your code: @GET
@Path("/form")
public TemplateInstance renderForm() {
return Templates.fullPageWithForm();
}
@GET
@Path("/form")
@HtmxPartial
public TemplateInstance renderFormPartial() {
return Templates.form();
} Now, lemme first go read up on htmx, but one example of Turbo I've seen from Rails wasn't exactly what you're showing here with full page containing element X versus partial render of X. The example I'd seen was with a full page containing an order, which had comments, and the comments where rendered as partials (which I understand to be tags in the context of Qute), so you could render the whole page, or offload to the comment partials. Let's spit out some pseudo-code here: public class Order extends PanacheEntity {
@OneToMany(mappedBy = "order")
public List<Comment> comments;
}
public class Comment extends PanacheEntity {
@ManyToOne
public Order order;
public String text;
}
public class Orders extends Controller {
public static class Templates {
// full page
public static native TemplateInstance order(Order order);
// partial
public static native TemplateInstance comments(Order order);
}
public TemplateInstance order(@RestPath long id) {
return Templates.order(Order.findById(id));
}
public TemplateInstance comments(@RestPath long id) {
return Templates.comments(Order.findById(id));
}
} In {#extend main.html}
{#set title "Order "+order.id}
{#comments order/}
{/extend} In {@model.Order order/}
<ul>
{#for comment in comments}
<li>{comment.text}</li>
{/for}
</ul> But this doesn't need any special support on the RR side, aside from perhaps allowing type-safe template tags. |
This is clever. Side-note, this is how I used global vars too (via CDI), but recent releases added support for them explicitely: https://quarkus.io/guides/qute-reference#global_variables |
How is
How are these two dissambiguated? Are they different paths? For |
They're not, but I don't think they need to be. Oh, this is renarde, so they have |
I need to look more into this, because in the demo I saw, it didn't seem to work quite like that. |
Hm... in the few code samples I saw things were controlled via some special HTTP headers. |
@FroMage do you look into htmx perhaps? |
Just to add a tiny note in case we do decide to go down the route of providing some kind of API that would allow for selection, that |
OK, so I finally read up on Turbo and was a bit perplexed by their docs, which I find confusing, as it implied you didn't have to modify your endpoint at all: you could just return the entire page with the normal endpoint and as long as you had a But the endpoint would be the same, and so its cost would be the same as well, say if it fetched stuff from the DB. Micronaut has a similar approach: https://micronaut-projects.github.io/micronaut-views/latest/guide/#turbo where the endpoint is shared: @Produces(MediaType.TEXT_HTML)
@TurboFrameView("form")
@View("edit")
@Get
Map<String, Object> index() {
return Collections.singletonMap("message",
new Message(1L, "My message title", "My message content"));
} But the views differ, because here is the full view, identified by <!DOCTYPE html>
<html>
<head>
<title>Edit</title>
</head>
<body>
<h1>Editing message</h1>
<turbo-frame id="message_$message.getId()">
#parse("views/form.vm")
</turbo-frame>
</body>
</html> And here's the partial view, used by the full view as an include, specified by the <form action="/messages/$message.getId()">
<input name="name" type="text" value="$message.getName()">
<textarea name="content">$message.getContent()</textarea>
<input type="submit">
</form> So in this case it's obvious we're not rendering the same template, but it's still the same endpoint. This doesn't really match what I had understood in the case where we're rendering several messages from a single page. Also I've no idea how they can surround the partial template with the required I also realise that htmx and Turbo appear to be different ways to achieve something comparable, but with different tech. |
From the Request/Response header docs of Htmx it appears that at a minimum we should be providing an API to read/write Htmx headers. So, yeah, Htmx and Turbo appear to be completely different technologies that happen to have a similar backend decomposition solution. We should probably do two separate extensions but keep the solution/API similar. |
Thanks for the insights @FroMage |
Wouldn’t it be better to create only support for conditional routes in resteasy. With such flexible foundation everybody could create solution for him with few lines of code. After that we could think about supporting specific frameworks. |
We'll see. If we do do that , I mentioned above what needs to be done. |
This annotation you're describing is very much a generalisation of content-type matching. If Htmx had a @GET
@Path("/form")
@Produces(MediaType.TEXT_HTML)
public Uni<String> renderForm(@Context HttpHeaders headers) {
return Templates.fullPageWithForm().createUni();
}
@GET
@Path("/form")
@Produces("text/htmx")
public Uni<String> renderFormPartial(@Context HttpHeaders headers) {
return Templates.form().createUni();
} And call this a day. In fact, we have request negociation/matching for URI,HTTP method,Accept and Content-Type, but that's pretty much it, they're all hard-coded. There's no user code that could help select methods, ATM. Adding an API for that for any user code would mean having conditional routing for rules that are not hard-coded. It would complexify our routing/matching algo (unless we can rewrite it using this new abstraction) but it would also be less efficient than static routing which we try to promote. Now, I'd like to see a real use-case for having separate endpoints, and not simply return the same full document as advertised by Turbo (they strip the outer elements) or do the filtering in the views like Andy showed. I'm thinking about real different logic. |
+1 |
The filtering in the template makes quite some sense:
I am currently evaluating a possible Quarkiverse extension to make it all integrated and easy to use: |
maybe i'm missing something but isn't the use case of having a endpoint that can either serve a json model response vs htmx client ui rendered response what gives quite different logic and thus having two different methods is nice ? |
Yeah, but ideally you want that to be dead simple |
Isn't that "use case" obvious? Don't run code you don't need. If you return the full view, you most likely run code you don't need. If you filter the output in the view, you either run code you don't need (same as previous option), or you let the view assume the role of the controller. (If that feels like "premature optimization" to some, I'm gonna claim this is not optimization at all. Not running code you don't need, when you know you don't need to run the code, that's just common sense.) Of course the argument above makes no sense when rendering the desired part of the view requires running 99% of the code you'd have to run for the full view. In my experience, that's seldom the case, but I admit my experience is mostly from rather dynamic websites with multiple independent parts contributing to the resulting page. |
Description
Hello,
I would like to create multiple endpoints with the same url. Depending on request headers I would like to decide which method should be used. Currently I have one endpoint with conditional return statement but it is not ideal solution for me.
It would be nice to create multiple endpoints with same url i and annotate them with custom annotations.
I was looking in docs and in the source code but I can't find interceptor which I could use. I think it is not yet possible.
My use case is related to Qute and Htmx library:
Depending on header presence I need to render whole page or only partial response.
Instead of this code I would like to create two separate endpoints and annotate them.
This is approach can be implemented for example in Spring (https://github.com/wimdeblauwe/htmx-spring-boot-thymeleaf).
It would like to recreate something similar in Resteasy but I need intercept endpoint choose decision.
I think there will be much more use cases for it. For example we could create API annotated with
@APIv2
without touching old endpoints and serve new API depending on request headers.@geoand @Ladicek @maxandersen
Implementation ideas
No response
The text was updated successfully, but these errors were encountered: