Beyond the Basics |
Cookies and Forms |
Christopher
Moderator
Member Rara Avis
since 1999-08-02
Posts 8296Purgatorial Incarceration |
I have a form, similar to this one, where I want to set cookies. I am thinking of using cgi to do this (avoiding javascript issues). Where/when are the cookies set and called from? I am assuming that they are set when the page for the form is called - somewhere when it is being processed by the browser...? If I want to do this through cgi, does the page itself need to be generated through cgi, or can I do some form of include like I would with javascript? Also, I have a "bio" section. I wanted to include that in the information returned to the user (assuming they've entered one before) which is in a <textarea> - is that possible? I don't think I've seen it which is why I'm asking. Forewarning - I picked up "Perl for Dummies" yesterday and am already a good portion of the way through it. You're safe for the moment, because it's some time before the book addresses integration with the Web... but it'll get there. Peace & thanks, C |
||
© Copyright 2001 C.G. Ward - All Rights Reserved | |||
Ron
Administrator
Member Rara Avis
since 1999-05-19
Posts 8669Michigan, US |
If you want to set cookies in Perl (as opposed to getting them), yes, the page would have to CGI generated. Setting cookies in a non-embedded language is done in the header, before the page is ever sent to the browser, and there are whole libraries dedicated to that purpose. I'm not entirely clear what you're asking about the bio section, especially if you don't think you've seen it before (click on the Edit icon and see if that rings a bell?)… |
||
Christopher
Moderator
Member Rara Avis
since 1999-08-02
Posts 8296Purgatorial Incarceration |
To the second I want to issue a resounding "duh!!!" TO the rest - please bear with me. I appreciate your patience to no end. What I'd like to do is make portions of the forms on my site: Remember the information the person put in last (on a per person basis of course), then when they open that page in the future, write it in form them so they don't have to do it again. ie: Name, email address, and bio for the content submissions form. My future goal, once I learn enough - however long that takes, LOL - is to create/modify existing program/s to a more customized end. As it stands right now, I'm at the mercy of pre-made programs. What this signifies is that I'm afraid/unable to modify the existing program which runs the handling of my forms. Can I: turn "form.htm" into "formcookies.cgi" - make "formcookies.cgi" set and recall cookies, then print out "results" into what the form previously looked like, including the <form action="formhandler.cgi"> tag? Or - as I'm thinking might be the case, would I need to have "formcookies.cgi" as the form output, then have that either a) handle the form instead of the previous progrram, or b) write the cookies then pass the information to "formhandler.cgi?" Hope I'm not being too big of a pain here. I'm looking for stop-gap measures until my knowledges catches up with my intentions as far as this goes. Thank you for your help. C |
||
Ron
Administrator
Member Rara Avis
since 1999-05-19
Posts 8669Michigan, US |
What you haven't said, Chris, might well be the answer to your question. Unless you intend to set some very large, very unstructured cookies, you're actually talking about using a database backend. When they come to the forms page, you determine who they are and then pull all the various information out of the database, using it to prefill the form? That's something that's been on my "list" for the main site, but my stop-gap measure was to provide a separate form at http://netpoets.com/submit/psubmit.htm for Resident Poets. As long as they use the same email address, I simply don't ask all the questions I already have on file. Eventually, this is the way it should work. After they submit a poem, the "Thank You" page will set a cookie, probably based on their email address. The next time they go to the submissions page, and before the page is displayed, a Perl program will read the cookie and base the display upon the results. If they have no email cookie, they get the normal page. If they have the cookie, however, the redundant fields will either be prefilled or just not displayed. But, yea, for that to work at all, the submissions page will have to be a CGI program. |
||
Christopher
Moderator
Member Rara Avis
since 1999-08-02
Posts 8296Purgatorial Incarceration |
Actually Ron, thank you. I hadn't thought of having a database, but pondering that for the past half hour or so I can see a huge benefit - for more than just passing cookies back and forth. But, that will be a little while in the future yet. LOL A bit beyond my present capabilities, but with the idea I can start working in that direction! Thank you for the idea!!! So - an opinion: I can use JavaScript to write and read cookies - would that be a realistic temporary measure, or is there a reason I shouldn't use it until I can do it through Perl? Peace, C |
||
Ron
Administrator
Member Rara Avis
since 1999-05-19
Posts 8669Michigan, US |
There are two downsides to JavaScript. One, of course, is that you're automatically disallowing those who have it turned off, unless you specifically provide other options. Two, it's always a struggle to make it work across ALL platforms. But those are equally true of ANY JavaScript use, and we somehow continue to manage. FWIW, in the forums, cookies are always set in JavaScript and read by both JavaScript and Perl. I suspect, however, that's because the original author of our software, Ted, just didn't feel comfortable with Perl cookies when he first started the code. I would (and eventually will) do it differently, and eliminate the JavaScript. |
||
Christopher
Moderator
Member Rara Avis
since 1999-08-02
Posts 8296Purgatorial Incarceration |
And we're comin' back around! I've spent no little time looking through my book, studying the cookie 'issues.' The actual use of them (and how they can relate to a database) seem pretty easy. However, my book only tells me how to set and read cookies using "cgi.pm" which I'm not all that hot on using. Can we perhaps go over it a bit? I'm sure if i get a good example and the basics, I can probably crunch through the rest myself. Peace n' Thanks |
||
Ron
Administrator
Member Rara Avis
since 1999-05-19
Posts 8669Michigan, US |
Cookies are both very easy and a major pain in the neck. When the remote client connects to a page, including your CGI page, the web server automatically gets all the cookies it has stored. Your program gets them as an ENVironment variable, so "reading" cookies is simply a matter of parsing the ENV string. Writing cookies is just a simple print statement. You send the Set-Cookie header, followed by your cookies, when the cookies should expire, and the path other programs can be in to read them. You can also clear an existing cookie by setting it with an expire date in the past. There are two things that make cookies a pain in the neck. First, the current system is VERY fussy about syntax. Failure to dot an i or cross a t results in squat. It took me forever, I remember, to get the date/time expiry formatted to its liking. Second, timing is everything. You can read cookies any time you like, but you must set them before you print anything else. Set-Cookie: is a special HTML header that the browser recognizes, but it IS a header and needs to sent as such. Specifically, it MUST be sent before you send the Content-type: text/html\n\n header. Otherwise, your cookies will just become part of the content printed on the browser page. Below is the subroutine I wrote last year for the main site. Instead of using multiple cookies, I use only one cookie with multi-values. It should look familiar to you, because it's just a pipe delimited database record like we've talked about previously. Your cookies are read from or written into a global hash called (duh) %cookies. In this case, I hard-coded in the names I wanted (aMail, aFirst, etc), which really isn't good practice. Having worked with the main site for several years, I pretty much knew exactly what I needed, but it can become a maintenance nightmare if you are less sure. Once you start passing out cookies to your visitors, changing hard-coded names packed into a single cookie like this will make old cookies useless. If you wanted to use several cookies, rather than a single delimited cookie, you'd have to change both the SET and CLEAR blocks into loops. # we use just one cookie (netpoets) to store several fields # this routine will SET, GET, or CLEAR that cookie # parameters are stored in the %cookie GLOBAL hash sub Cookies { my $action = $_[0]; # action to perform = set, get, clear my (@cookies, $name, $value, $key); if ($action =~ /get/i) { # GET cookies foreach (split(/; /, $ENV{'HTTP_COOKIE'})) { s/\+/ /g; s/%([A-Fa-f0-9]{2})/pack("c",hex($1))/ge; ($name, $value) = split(/=/,$_,2); if ($name eq "netpoets") { @cookies = split(/\|/, $value); $cookie{aMail} = $cookies[0]; $cookie{aFirst} = $cookies[1]; $cookie{aLast} = $cookies[2]; $cookie{authorID} = $cookies[3]; $cookie{aPassword} = $cookies[4]; } else { $cookie{$name} = $value; } } } elsif ($action =~ /set/i) { # SET cookies $value = "$cookie{aMail}\|$cookie{aFirst}\|$cookie{aLast}\|$cookie{authorID}\|$cookie{aPassword}"; $Expires = "Wed, 31-Dec-2003 00:00:00 GMT"; print qq~Set-Cookie: netpoets=$value;expires=$Expires;path=/;\n~; } else { $value = ""; $Expires = "Sun, 31-Dec-2000 00:00:00 GMT"; print qq~Set-Cookie: netpoets=$value;expires=$Expires;path=/;\n~; undef %cookie; } } # end sr Cookies |
||
Christopher
Moderator
Member Rara Avis
since 1999-08-02
Posts 8296Purgatorial Incarceration |
I don't know whether to be scared or excited that this doesn't look like a foreign language to me anymore... I have two initial questions that I'd like to ask here. The first is - can you just use a database file to contain all the information and just use the user's id to pull the other information (since you already have it on file)? I understand that this wouldn't work with someone who isn't logged in as a member (ie: # of topics to list in the forums when they aren't logged in as a member) but I would think it would work for other reasons? That way you only need to put a single cookie in, which would pull the remaining info off file. Second: @cookies = split(/\|/, $value); $cookie{aMail} = $cookies[0]; $cookie{aFirst} = $cookies[1]; $cookie{aLast} = $cookies[2]; I get the overall concept here, but am not understanding why the listing is $cookie{name} = $cookies[#]? I understand that you're defining which portion of your array is equal to its relative value... but I'm confused about $variable{brackets} bit...? why don't / can't you just label it as $name = $cookies[1] ? Otherwise, I think i'm cool... oh, nope, one more question - why aren't you processing your ENV with the rest of your input? Thanks |
||
Ron
Administrator
Member Rara Avis
since 1999-05-19
Posts 8669Michigan, US |
1) Yes, you can and should put just about everything in a database rather than cookies. Every application is a little different, but the usual reason to store more than just a single ID in the cookie is for authorization. If you have a password-protected area, you need to do authorization on EVERY page in that area, else people will sneak in through side-doors, and that can amount to a lot of disk reads. Put the authorization data in cookies, however, and you can shift that burden off the web server. 2) Hashes are often called named arrays, because the {name} part replaces the [index] value in a normal array. Kind of like an unordered list, rather than an ordered one. But still a list, and as such, often more useful than simple scalar variables when working with similar data. For example, I couldn't loop through a list of cookies if they were just scalars, but I can when they're in a list. 3) Define "with the rest of your input." Your program will usually process the cookie ENV "with" the rest of your input in the chronological sense of them being sequential events, bang, bang, but they are still functionally different things and each deserves its own subroutine. Think of it this way - a year from now you may write another application that has to process input data, and you'll be ahead of the game if you already have a function written that does that. If that processing routine has cookies and other things thrown into it, though, you may find it needs a lot of rewriting to work in a different app. Ideally, each subroutine you write should do ONE and only one thing. Modularity. |
||
Christopher
Moderator
Member Rara Avis
since 1999-08-02
Posts 8296Purgatorial Incarceration |
Cool. I love it when this stuff makes sense. #1 is clear, will look more into #2 to make sure I understand it, and #3 - gotcha. That's what I meant. It makes sense to separate it for that reason. Thanks and I'm sure I'll be back teach. |
||
Christopher
Moderator
Member Rara Avis
since 1999-08-02
Posts 8296Purgatorial Incarceration |
uhm... how do i go about - using this sub in the way it's written (i think i could handle it separating things) - pass what $action i want it to do? beating my head, but am not getting anywhere on this part! |
||
Christopher
Moderator
Member Rara Avis
since 1999-08-02
Posts 8296Purgatorial Incarceration |
ok - i guess what i'm asking, is if there is a way to pass a subroutine with values... like you can with a link or submit field: <a href="cookies.cgi?action=get>get cookies</a>... but in a subroutine: &Cookies?action=get - dunno... something like that. still looking in m'book, but am not finding anything along these lines yet. |
||
Ron
Administrator
Member Rara Avis
since 1999-05-19
Posts 8669Michigan, US |
Yes, you can pass values into a routine (with some limitations). You can get values back out of a routine, too (with the same limitations). You pass values into a subroutine like this: Cookies("set"); or, with multiple values, like Routine($value1, $value2, @array). The way you've been calling routines, with the leading ampersand (&Routine ;) is deprecated and will eventually be replaced with Routine() (even with no parameters passed). The values that you pass in the parenthesis of your subroutine call are placed into the special Perl array @_ and it's entirely up to your subroutine what to do with them. Notice in the Cookie subroutine, the first thing that happens is that $action is assigned to $_[0]? If there were more scalar values being passed, the routine would simply grab each subsequent value, so you might see $value2 = @_[1] and $value3=$_[2]. Another way to do the same thing, and one I use a lot, would be ($value1, $value2, $value3) = @_. This depends on the fact that scalar variables enclosed in parenthesis are equivalent to a list and is functionally no different than the way many arrays are assigned [such as @array = (1, 2, 3, 4)]. It just reverses the concept. What you'll see in a lot of programs, instead of a direct assignment, is the shift operator. It's very common to do $action = shift; $value2 = shift; etc. The shift operator pulls the leading value off an array (and @_ is the default array if none is specified), and I mean literally pulls. After a shift operation, what was the second element of the array has become the first element. You'll rarely see me use the shift operator to grab parameters for that very reason - it's moving the whole array with every execution and is one of the slowest of all Perl's operations. Explicit assignment is much faster. The exception to that rule, however, is when you pass an array. Notice in my second paragraph above that I said we could something like this: Routine($value1, $value2, @array)? This is perfectly legal and common, but it's also a little tricky to understand the limitations. You are essentially passing an array INSIDE of another array (the special @_ array). That will only work if the array is the LAST parameter you pass and, by extension, that means you are limited to passing only one array into a subroutine. Getting those parameters back out of the @_ array then makes good use of the shift operation. $action = shift; # pull first element off the @_ array $more = shift; # pull what has become the first element off @rest = @_; # get everything still left in the @_ array Returning values from a subroutine follows basically the same path, though it does not involve the special @_ array. If you call a subroutine like $returnValue = Routine();, then your Routine should end with a return $something;. Multiple return values follow the same rules as when passing variables. ($value1, $value2) = Routine(); should be matched with return ($some1, $some2); @array = Routine(); should be matched with return @something; [or] @array = Routine(); might be matched with return ($some1, $some2, $some3); ($value1, $value2, @array) = Routine(); might even be matched with return @bigArray; The rule of thumb is that passing values into a routine, or returning values from a routine, always involves an array - even if the array isn't obvious. In my last example above, I'm taking a bigger array, assigning the first two elements of it to scalar variables and the rest to a smaller array. It's still all an array, and how I break them up is my call. Now, to answer your REAL question. :) To use the Cookie subroutine, you should first assign the values you want to the %cookie hash. This is a global hash because - guess what? - the one thing you can't easily pass to a subroutine is a hash. Once the hash is assigned, you can then call Cookie with the appropriate $action parameter. Cookie("set"); # prefill the hash Cookie("get"); # the hash will be filled FROM the cookies Cookie("clear"); # hash isn't even referenced |
||
Christopher
Moderator
Member Rara Avis
since 1999-08-02
Posts 8296Purgatorial Incarceration |
Took me durn near all night, but I finally got it (at lesat so far, lol). Ironically, my largest problem wasn't with the issues specific to cookies, but from pulling from the hashes. Took me some time to get that part figured out. *whew* I really like the method you've listed above - I tested and it seems to work very well, without issues... however, I'd like to ask for safety's sake if there is an issue with switching everything over to the above call method now? I doubt it, but feel better safe than sorry - would hate to change everything only to find out I needed to change it back later, lol. Thanks Ron. I really like the way you help, allowing me to push myself to learn, while not leaving me hanging when I'm stuck! |
||
Christopher
Moderator
Member Rara Avis
since 1999-08-02
Posts 8296Purgatorial Incarceration |
oh, and for future reference, Windows XP stores their cookies in C:\Documents and Settings\Owner\Cookies. That took half my time tonight, finding where they were stored! |
||
Christopher
Moderator
Member Rara Avis
since 1999-08-02
Posts 8296Purgatorial Incarceration |
oh, I like this, I like this!!! openFile($handle = "MEMBER", $filePath = "<members/$aID/$aID.dat"); sub openFile { open ($handle, "$filePath") || die "Couldn't open $filePath: $!"; @index = <$handle>; close ($handle); } *happiness* [This message has been edited by Christopher (12-05-2002 08:57 AM).] |
||
Ron
Administrator
Member Rara Avis
since 1999-05-19
Posts 8669Michigan, US |
quote: Still on my first cup of coffee, so I better verify - you're talking about Routine() as opposed to &Routine? If so, nope, there's certainly no hidden issues I've ever seen. |
||
Christopher
Moderator
Member Rara Avis
since 1999-08-02
Posts 8296Purgatorial Incarceration |
Yep - that's what I was talking about ( routine() vs. &routine ) and cool, thank you! |
||
Ron
Administrator
Member Rara Avis
since 1999-05-19
Posts 8669Michigan, US |
Mmmm. I've never "quite" tried what you're doing in that openFile subroutine, Chris, but would offer a little caution. File handles are treated very differently than other Perl variables and there could be some strange side-effects of using a scalar to stand in for a file handle. Like I said, I've never tried it, but I would sure experiment a bit before I started doing it very much. More importantly, though, it's not necessary. The file handle is used ONLY within the subroutine and then is immediately closed. Why pass it at all? And, uh, all you're really doing so far is passing global variables. Your routine, as written, can be called only for ONE file at a time. If I call it to read a Profile, the @index variable gets filled with Profile data. If I call it again right away, the @index variable will no longer hold my Profile data, but will be overwritten with new data. That's the disadvantage of making @index a global variable. By using the stack and limiting the scope of the variables within the subroutine, we can make it more useful. my $filePath = "<members/$aID/$aID.dat"; # keep it local in scope my $other = "<master.dat"; my @profile = openFile($filePath); my @params = openFile($other); sub openFile { my $thisPath = $_[0]; # pull passed parameter off the stack open(FILE, $thisPath) || die "Couldn't open $thisPath: $!"; my @index = <FILE>; close (FILE); return @index; # send the array back to calling code } |
||
Christopher
Moderator
Member Rara Avis
since 1999-08-02
Posts 8296Purgatorial Incarceration |
I've tried it in several different applications now, Ron, and I've seen nothing I wasn't expecting. Uhm - but a lot better not to need it, I'd say. I was looking at it several minutes ago and thinking part of what you suggested - localizing. the subroutine you wrote makes complete sense - uhm, except for the first part. What array are we pulling from here? I'm not really understanding how you're setting up this "stack" (assuming that's an array?). i can see it as: ---- @fileArray = ("path/one.dat", "path/two.dat", "path/three.dat"); ---- openFile($filePath = $fileArray[0]); ---- but it doesn't look like that's what you're suggesting with this list below: my $filePath = "<members/$aID/$aID.dat"; # keep it local in scope my $other = "<master.dat"; my @profile = openFile($filePath); my @params = openFile($other); |
||
Ron
Administrator
Member Rara Avis
since 1999-05-19
Posts 8669Michigan, US |
The "stack" is just programmer lingo, 'cause it's easier than calling it the at-sign-underscore array. But that's what it really is, our old friend @_ When you pass values to a subroutine, Perl automatically builds the @_ array for you, filling it sequentially with the values you specified. By the time execution of the program reaches the subroutine, it's simply there for you to use. Magic. So, the very first thing any subroutine should do is pull the variables it's expecting off of the "stack." It might help to understand the procedure if you understand WHY Perl does it this way. One of the major goals of structured programming is to limit the scope of variables, for many of the reasons we talked about previously. Specifically, you don't want a variable in RoutineA to be changed by some other subroutine it calls. We want the variables declared with "my" in RoutineA to STAY in RoutineA. So, when we call openFile($filePath); Perl does NOT really pass the variable $filePath to the subroutine being called. If it did that, the openFile routine could change it and that's a no-no. Instead, it takes the CONTENTS of the variable, the value, and pushes it into the special @_ array. The subroutine gets a COPY of the variable being passed, which is should immediately pull off the stack and assign to a new variable name. It can then change its copy of RoutineA's variable all it wants, without ever affecting the real RoutineA variable. |
||
Christopher
Moderator
Member Rara Avis
since 1999-08-02
Posts 8296Purgatorial Incarceration |
ok - I'm stopping after this, because I need some sleep, lol, but after several frustrating moments, I think I might have it - my $thisPath = $_[0]; what I'm understanding is taht $thisPath has absolutely nothing to do with the variable you listed in routine($path = path/file.ext); but is what you're assigning the first value passed by the routine into the sub as? so would this be valid (and secondly, practical?): ----- @fileLoc = ("path/one.dat", "path/two.dat", "path/three.dat"); openFile($filePath = $fileLoc[1]); sub openFile { my $thisPath = $_[0]; # would be the value of $fileLoc[1] open(FILE, $thisPath) || die "Couldn't open $thisPath: $!"; my @index = <FILE>; close (FILE); return @index; # send the array back to calling code } ok - I'll go get some sleep now, lol... wish I still drank Mt. Dew. Sigh, giving up vices gets you in the end. ps - thank you again |
||
Ron
Administrator
Member Rara Avis
since 1999-05-19
Posts 8669Michigan, US |
You've essentially got it, Chris, though I think you're needlessly complicating it with unnecessary assignments. This is equivalent to what you just did, but somewhat simpler. @data = openFile("path/one.dat"); Whatever is within the parenthesis in our subroutine call, in this case the literal string "path/one.dat", will be pushed onto the stack for the subroutine to pull off. When the subroutine hits the line my $thisPath = $_[0] the variable $thisPath will then hold "path/one.dat" And who says Mt. Dew is a vice??? |
||
Christopher
Moderator
Member Rara Avis
since 1999-08-02
Posts 8296Purgatorial Incarceration |
gotcha Ron. Actually, what i was looking at doing was making an array with several different file names / paths in it, to later be referred to by $variable[3] etc., that way i could avoid repeated typing of the path info (as it is repeated several to many times throughout the script). hmm - so i see, i could still do that: @data=openFile($variable[4]); man i love this stuff. and yes, Mt. Dew is a vice... and a VERY difficult thing for me to learn to moderate! |
||
⇧ top of page ⇧ | ||
All times are ET (US). All dates are in Year-Month-Day format. |