preliminary minimal changes#251
preliminary minimal changes#251zjmarlow wants to merge 32 commits intoraku-community-modules:mainfrom
Conversation
|
Appears to be failing 2 tests for me on MacOS: |
|
Should HTTP::UserAgent be producing valid Messages, or is it up to the users of the library to use it in such a way that it only produces valid messages? Some of the test messages are themselves invalid, so what needs changing will depend on the library's purpose. |
|
Well, that is a good question. What this module really needs, is a maintainer. Would you be willing to take on that role? You apparently have a need to fix things in it :-) |
|
Yes, but I'll need guidance and feedback as I maintain it, if that's okay? Some first questions:
I'll start looking through the other open issues, too. |
|
What would the best channel / medium be for discussing this module? |
|
Either #raku on libera.chat or on Discord |
|
There are enough changes to get things conformant that it would be difficult not to break existing behavior. My plan is to provide alternate classes that can be imported with:
if that is acceptable, though I'm open to other options. |
|
That would make it opt-in, and as such backward compatible. 👍 |
|
I wasn't able to get exporting to work how I was hoping, so instead, the strict versions are just named after the originals with "-Strict" attached. They are subclasses of the originals. The new classes are:
They are all made available with
so that it is easy to just import the strict classes. Behavior when mixing strict and original classes is "undefined". The only change to the original should be the is-chunked multi methods in the HTTP::Message class. One checks the message's own headers, the other takes an arbitrary header object. The following new testcases were added:
I still need to do some testing with the HTTP::UserAgent-Strict class before turning this draft request into a full pull request, but this should be a good place for review and verifying testcases (the new behavior passed for me locally across Windows, Linux, and Mac). |
|
need to fix body-less messages. will comment again once fixed. |
| unit class HTTP::Cookies; | ||
|
|
||
| use HTTP::Cookie; | ||
| use HTTP::Response:auth<zef:raku-community-modules>; |
There was a problem hiding this comment.
I think these were qualified like this because there are un-related packages that provide similarly named modules, so I'd be careful about removing that.
There was a problem hiding this comment.
Yeah, the removal of this will requires an explanation
There was a problem hiding this comment.
I missed that one. It is added back now. I wasn't aware of the similarly named modules.
|
My apologies. I am now reading through the META6.json and distributions documentation and committed some fixes. Will the version's patch number eventually need to be updated? |
|
Okay, the META6.json should be fixed. I did not change the version number. |
|
Sorry, I was too quick with the auto-complete. It was meant to be in reference to #226 , which was opened by bbkr. |
|
Testcases added for the various UserAgent and Request strict / non-strict interactions. |
ugexe
left a comment
There was a problem hiding this comment.
That's quite a lot of changes, nice! Unfortunately making lots of changes also has drawbacks... would it be possible to clean up this PR further? There are multiple commits fixing things broken in/from previous commits which makes reviewing things commit by commit frustrating. The PR title is still "preliminary changes" and is described as being a draft pull request (among other things), which makes understanding the git history more difficult. There are also many leftover comments in the code itself.
I left quite a few comments, but that was just from a cursory review so I suspect there are things I missed. Hopefully it is not too discouraging... you've bitten off a rather large piece of work.
|
|
||
| method new($content?, *%fields) { | ||
| my $header = HTTP::Header.new(|%fields); | ||
| method new($content?, Bool :$strict, *%fields) { |
There was a problem hiding this comment.
This seems like a bit of an unfortunate api design. What if I want to send a header named strict?
There was a problem hiding this comment.
Instead of named, strict could be the final positional and default to False. If that's acceptable, I'd like to keep it consistent by changing all the signatures to have strict as the final positional with False as default. This particular case might require a multi method, though:
multi method new($content, Bool $strict = False, *%fields)
multi method new(Bool $strict = False, *%fields) is defaultThere was a problem hiding this comment.
I haven't thought much about it, but another option would just be to make setting strict an additional step, i.e. my $m = HTTP::Message.new(...); $m.strict(True);. I think either way is fine, but maybe you have an opinion on it.
There was a problem hiding this comment.
I think, aside from simplicity, good goals would be:
- don't break existing code
- clear
- consistent
- convenient
I hadn't thought of your option, but think it does better with the priorities than what I had:
Message.new: 'some content', True, field => 'value';
which fails 2, in my opinion, and complicates 1.
| # pop zero-length Str that occurs after last chunk | ||
| # what to do if this doesn't happen? | ||
| @lines.pop if @lines %2; | ||
| @lines = grep *, |
There was a problem hiding this comment.
What is the purpose of grepping on *? It is also hard to read this chain since it is mixing routine and method calls.
There was a problem hiding this comment.
The full purpose of the chain is to take lines two at a time - first line is the chunk length, second the chunk content. If the chunk length looks valid, pass along the content, otherwise use grep to filter it out. It should probably fully validate.
The larger problem is that if the content contains embedded CRLFs, the method is broken. I might try my hand at a correct implementation.
There was a problem hiding this comment.
Let me rephrase my question: what do you think grep *, @foo does? If it doesn't do anything is there any reason to include it?
There was a problem hiding this comment.
It was meant to filter out falsy values, but that requires grep so *. That was pushed for one of the instances. The other is fixed locally.
| } | ||
|
|
||
| method parse($raw_message) { | ||
| method !parse ( $raw_message ) { |
There was a problem hiding this comment.
!parse isn't a great name for this function, especially given there is a public function of the same name. !parse-strict or some such would make it more clear what the purpose of this function is.
Also it seems like there is a lot of logic that could be shared between !parse and parse. Refactoring these would make it easier to maintain.
There was a problem hiding this comment.
refactored, including adding various private methods that parse status / request line, header, and content
| # technically incorrect - content allowed to contain embedded CRLFs | ||
| my @lines = $content.split: $CRLF; | ||
| # pop zero-length Str that occurs after last chunk | ||
| # what to do if this doesn't happen? |
There was a problem hiding this comment.
I don't know, what should this do?
There was a problem hiding this comment.
One option would be: if the user specified throw-exceptions, to throw with 'truncated last chunk'. The changes are meant to focus on producing strict output rather than demand strict input, so I'm not sure about this - your thoughts?
There was a problem hiding this comment.
I'm not sure. It would make sense to create an issue though so it isn't forgotten/
There was a problem hiding this comment.
It seems like a strange design to only have special http header objects for one type of header. Is there any reason this needs its own object? The motivation for it isn't clear to me as the commit message on this file is just "initial strict implementation".
There was a problem hiding this comment.
ETags can be tagged as weak validators and might be handled differently in those cases.
| # headers container | ||
| has @.fields; | ||
|
|
||
| grammar Grammar::Strict { |
There was a problem hiding this comment.
The other grammar in this file is called HTTP::Header::Grammar which makes it clear what it is used for. To me a name like Grammar::Strict in the same file does not suggest it has any relation to HTTP::Header::Grammar even though it appears that is the case based on the parse method that returns one of these classes.
There was a problem hiding this comment.
I don't think I understand the comment. The only relation between the grammars is that they serve the same purpose. Otherwise the strict grammar is based on the RFCs and supports weak ETags. Could you please let me know if my explanation missed the mark?
|
Hello @ugexe , thank you for taking the time to do this. Would you prefer a separate PR with only the final changes committed for any further reviews? I have some other work to take care of so it will likely be over the next few days that I get through the change requests. |
|
I think the test failure is just due to incorrect skip count. I've fixed it locally. I'm still working through the change requests and will mention in a comment when changes have been pushed. In the meantime, I might have questions / need feedback. |
|
Okay, all changes I think I understood have been applied. I can push what I have now, or, if you prefer, wait until the rest of the change request conversations have been resolved. |
Yes please. |
|
The signature change where strict is the last positional instead of an associative has been implemented enough to get tests passing. The rest of the methods should probably be updated at some point to make requesting strict processing consistent. |
|
Final sig changes submitted. In the end, I kept the strict parameter as the last positional, and optional. Message, Request, and Response constructors all need to know at the time of call what the strict setting is. |
|
Please let me know if continuing to pursue these changes is worthwhile. If not, I don't mind continuing to contribute in other ways (there are two other small pull requests ready for review). |
|
@zjmarlow I haven't read through the entire discussion in detail, but have loosely followed from the sidelines. My impression has always been that a lot of work and thought (from you and the reviewers) went into this. Unless I somehow missed a difficulty or controversy that questions all of this work, I'd find it sad to just lose all of this work all of you have put into this. |
ugexe
left a comment
There was a problem hiding this comment.
Sorry for taking so long to re-review. The changes look good, but I think there are still a few bugs that should be fixed. I would encourage you to rebase this branch into a few logical commits so the git history is useful, but I wouldn't block this PR on that.
| my constant $max_size = 300; | ||
| my $s = $.header.Str($eol); | ||
| $s ~= $eol if $.content; | ||
| self.field: Content-Length => ( $!content.?encode or $!content ).bytes.Str |
There was a problem hiding this comment.
It feels wrong for a serialization function (the Str() function this logic is inside) to mutate the object (i.e. self.field). It also makes it confusing what the state of the object is if someone e.g. calls Str() multiple times.
There was a problem hiding this comment.
Okay, UA's request method handles some of the other Request setup - currently Cookies, User-Agent, and Authorization. It could be moved there.
There was a problem hiding this comment.
I think there are two conflicting principles here. One, as you mentioned, is that serialization should not mutate the object. The other is that, to conform to the RFCs, and thus respect a strict call, Messages must include Content Length in the absence of Transfer Encoding: chunked, and also use CRLF as the EOL.
One option would be to remove the strict option from Message.Str and have UA's request method handle including the Content Length and EOL selection when the request method is called with its strict option. I am open to other alternatives.
There was a problem hiding this comment.
I'll look into adding Content-Length if it's needed without mutating the object itself.
| method Str (Bool $strict is copy = False, :$debug, Bool :$bin) { | ||
| $strict ||= $!strict; |
There was a problem hiding this comment.
I'm not sure if many of these ||= instances are doing what you want or the user would expect. For instance I would expect the value of $strict to be used over $!strict, i.e. I expect function parameters to override class attributes representing a default. I would expect these to instead be like
| method Str (Bool $strict is copy = False, :$debug, Bool :$bin) { | |
| $strict ||= $!strict; | |
| method Str (Bool $strict is copy = $!strict, :$debug, Bool :$bin) { |
This way if I call .Str(False) (which as an aside doesn't read very well, and to me suggests it and maybe others should be a named argument where it makes sense unlike that previous .new instance from a prior review) the False is respected.
| # technically incorrect - content allowed to contain embedded CRLFs | ||
| my @lines = $content.split: $CRLF; | ||
| # pop zero-length Str that occurs after last chunk | ||
| # what to do if this doesn't happen? |
There was a problem hiding this comment.
I'm not sure. It would make sense to create an issue though so it isn't forgotten/
| } | ||
|
|
||
| multi method field ( HTTP::Header::ETag:D $etag ) { | ||
| @.fields.push: $etag; |
There was a problem hiding this comment.
Is this going to accumulate etags every time it is called? The other field setter above replaces existing values, it doesn't append.
There was a problem hiding this comment.
Yes, good point. To be lenient in what it accepts, it should either ignore or replace. To be consistent with the other behavior, it should probably replace.
There was a problem hiding this comment.
This change has been made locally. It needs some testcases to cover it. It will be committed with other changes once some more discussion has been had regarding the other change requests.
| method Str($eol = "\n") { | ||
| multi method Str(Str $eol is copy = "\n", Bool $strict is copy = False) { | ||
| $strict ||= $!strict; | ||
| $eol = $CRLF if $strict; |
There was a problem hiding this comment.
It is kind of a weird api to have $strict override $eol that the user explicitly passes. Something like this is a bit better since it respect the $eol the user provides, although it isn't great either:
multi method Str(Str $eol is copy = $strict ?? $CRLF !! "\n", Bool :$strict is copy = False) {
Also by making :$strict a named parameter we can call self.Str: :$strict; in the Str multi below instead of self.Str: "\n", $strict; (which duplicates the default \n in the function body as well as this multis signature).
There was a problem hiding this comment.
This issue seems similar to what is mentioned in the Message.parse comment. Waiting for more discussion in that comment.
|
No problem, I will work on rebasing and then go through the change requests this week. |
chunk size should be hex Co-authored-by: Nick Logan <nlogan@gmail.com>
|
For the rebase into logical commits, are you mainly referring to squashing commits that were minor / ended up being reverted / reworked? |
Essentially yes. That might end up being just a single commit. |
keep original names fix expected error message reorg modules; fix imports; restore original modules make sure strict classes pass original tests fix variable redeclaration fix body-less Message.Str was test issue auth reinstate auth; add Str as URL convenience methods to UA-Strict auth update provides update provides fix return fix return add UA-Strict itself to provides -Strict to ::Strict rename nested grammar add back auth flag implementation of strict remove redundant attr; fix new; fix strict print logic; add interop tests TestServer location pre strict sig change refactor Message parse methods; make $strict default to $!strict in Header pass $strict when sending binary request, too preliminary sig changes final sig changes Update lib/HTTP/Message.rakumod chunk size should be hex Co-authored-by: Nick Logan <nlogan@gmail.com>
…useragent into issue-226-eol-minimal
|
For your commits you'll want a reasonable message as well. Don't make a list of the changes you made - the changes should already be split up into logical pieces as commits. The commit message should inform us of the WHY and potentially some of the high level HOW. The commit message title should briefly explain what the changes do. For example something like:
The reason is someone looking at the git history of any of the lines of code you added should be able to understand why that line of code was added from the commit subject/message. |
|
Okay, I can amend the commit message. I understand that quite a bit of work has gone into the current approach, but it might be worth reconsidering the subclass approach. The two main goals were to 1. make changes opt-in / leave existing behavior in place, and 2. make requests more conformant to the RFCs. The drawbacks of the current approach are that several changes are being made to existing code opening the possibility of breaking existing behavior and that there are several places that a user can / has to specify which behavior they want. The subclass approach would leave existing code relatively untouched, provide the relevant classes with a single I am okay with continuing with the current approach, but if you think it's worth revisiting subclasses, please let me know - a separate pull request could be submitted and the one not used could be closed. |
|
It is really up to you. However, do consider the maintenance burden of whatever approach you take. As you've probably noticed there isn't a lot of bandwidth available for reviews here, and duplicated logic (on the surface) makes me think it is going to be a more difficult to maintain approach. But I could also see someone else arguing the opposite. |
Make requests more conformant to the RFCs Previously, HTTP::UserAgent would produce output that is technically invalid according to the RFCs. These (squashed) commits add a parameter to make requests more conformant. When the parameter is set to True, CRLFs will be used for EOLs and no extra, invalid EOL will be added to POST requests. The changes should not alter the functionality of code already using this module. Tests have been added to cover the changes. Also, ETags now have a separate class with an attribute indicating whether they are weak validators. Co-authored-by: Nick Logan <nlogan@gmail.com>
|
proposed commit message:
|
draft pull request with minimal changes to handle eol. also updates is-chunked check to include the case where multiple Transfer Encodings are present (chunked should always be last).
requesting feedback before final changes.