cburns has asked for the wisdom of the Perl Monks concerning the following question:

I have a subroutine:
sub msg { $msg_num = @_[0]; $msg_txt = "$msg_num $msgs{$msg_num}"; print "HTTP/1.0 $msg_txt\n"; }
It accesses this hash:
%msgs = ("200", "OK", "400", "Bad Request", "403", "Forbidden", "404", "Not Found", "500", "Internal Server Error");
If i call it directly from my program...
&msg(400);
it works:
HTTP/1.0 400 Bad Request
However, if I call it from an if statement...
if ($proto ne "HTTP/1.0") { &msg(400); }
it doesn't fully execute (the error text doesn't get printed):
HTTP/1.0 400
Any thoughts?
Thanks

Replies are listed 'Best First'.
(jcwren) Re: calling a sub from an if statement
by jcwren (Prior) on Jul 23, 2000 at 23:17 UTC
    Two things: Do you really know that your if statement is succeeding? When you have doubts about things like that, putting a print, die(), or some other statement that removes the question of whether your subroutine is actually getting called is needed.

    The second thing is the ampersand really isn't needed in the context that you're calling msg(). Typically, when you're passing arguments to a subroutine, the ampersand is not used. It's used when you want to pass the current value of @_ to the subroutine.

    I ran your code, setting $proto = "HTTP/1.0", and then to "xHTTP/1.0". In both cases, it ran fine. This leads me to believe that the value that you think should be in $proto is not what you expect. To test your theory, put a print or die() statement after the if that will display the value of $proto.

    --Chris

    e-mail jcwren
Re: calling a sub from an if statement
by steveAZ98 (Monk) on Jul 23, 2000 at 23:37 UTC
    I believe that your msgs hash may be out of scope when you call msg. I tried your code and ran into the same problems. I rewrote it like this
    my $msgs = { '200' => 'OK', '400' => 'Bad Request', '403' => 'Forbidden', '404' => 'Not Found', '500' => 'Internal Sever Error', }; sub msg { print "HTTP/1.0 $_[0] $msgs->{$_[0]}\n"; } if (1 == 1) { msg(400); }
    And it seems to work fine.

    On a side note you don't need double quotes around your hash vars, as they're not interpolated. Just something I learned and thought I'd pass on. It is supposedly a performance hit, although I've never tested it myself.
      Steve, you're code is definitely cleaner to look at, but I'm curious what problems you had running it the original. I copied it verbatim from the HTML, deleted the non-code, and ran it. I got two messages, one from the lone &msg(400), the other from the true if(), since $proto is undefined (no use strict and no -w. *gasp*).

      I then defined $proto with "HTTP/1.0", and got only one message, since the if() failed.

      What problems were you running into?

      --Chris

      e-mail jcwren
Re: calling a sub from an if statement
by le (Friar) on Jul 24, 2000 at 00:01 UTC
    I just wanted to say that
    sub msg { $msg_num = @_[0]; # ...
    is not necessarily what you want.
    @_[0] # An array slice $_[0] # The first element of the array @_ (usually passed to the subro +utine)
(jcwren) Re: calling a sub from an if statement
by jcwren (Prior) on Jul 24, 2000 at 00:07 UTC
    tye is correct, however, his solution leaves a touch to be desired. Simply move the %msgs has to the top of the file. And I would recommend taking SteveAZ98's method of initializing the hash. Using => is a lot cleaner than the comma separated key/value pairs. Much more obvious which messages goes with which key, particularly if you decide to add to the list.

    You could also simplify in a few places. Where you have
    if ($method ne "GET") { if ($method ne "POST") { msg(400); } }
    use instead
    if ($method ne "GET" && $method ne "POST") { msg(400); }
    Note that you probably want a return or and exit at this point, otherwise, you're going to continue executing code, after you've kicked an error message out. This is usually not what you want to do.

    You can also simplify your file reading with this:
    open(FILE1, $ARGV[0]); @lines = <FILE1>;
    And, of course, as a responsible programmer, you'll be wanting to close the file when you're done with it. <G>

    --Chris

    e-mail jcwren

      I don't know what you don't like about using BEGIN, perhaps you could elaborate. As for moving %msg to the top of the file, I specifically avoided that route because there are several problems with it. First, it can be hard to maintain. One little pass through moving things around to try to clean up your code and suddenly you've got a call to the sub before the data is initialized again.

      Second, I consider it a bad habit since when you get into more advanced coding, it isn't sufficient. For example, if this becomes a module:

      package My::Module; # Keep this next line at the very top, please: %msgs= (400=>"Bad Request",...); [...] use My::OtherModule; [...] sub msg { die "HTTP/1.0 $_[0] $msgs{$_[0]}\n"; }
      Then all we need is:
      package My::OtherModule; [...] sub import { msg(400) if ! $valid; [....] }
      and we run back into the same problem.
Re: calling a sub from an if statement
by tye (Sage) on Jul 23, 2000 at 23:58 UTC

    It isn't that %msgs is out of scope (that would generate an error). It is that sub msg is being called the first time before %msgs has been initialized.

    You could fix that with:

    my %msgs; BEGIN { %msgs= (400=>"Bad Request",...); }

Re: calling a sub from an if statement
by cburns (Novice) on Jul 23, 2000 at 23:51 UTC
    Tried you suggestions, to no avail. Here's all the code, incase something else is causing the problem:
    #!/usr/local/bin/perl # this is the code for sever.pl, my mini-server my @lines; open(FILE1, $ARGV[0]); while (<FILE1>) { push(@lines, $_); } ($method, $url, $proto) = split(/ /, $lines[0]); if ($method ne "GET") { if ($method ne "POST") { msg(400); } } if ($proto ne "HTTP/1.0") { msg(400); #here's where sub msg doesn't work } if ($method eq "POST") { open(FILE2, $ARGV[1]); while (<FILE2>) { $ENV{'REQUEST_QUERYSTRING'} = $_; } print "the querystring is: $ENV{'REQUEST_QUERYSTRING'}\n"; @q_string = split (/&/, $ENV{'REQUEST_QUERYSTRING'}); foreach $pair (@q_string) { my @temp = split (/=/, $pair); $param{$temp[0]} = $temp[1]; print "$temp[0] = $param{$temp[0]}\n"; } } %msgs = ("200", "OK", "400", "Bad Request", "403", "Forbidden", "404", + "Not Found", "500", "Internal Server Error"); msg(400); #here's where sub msg works sub msg { $msg_num = @_[0]; $msg_txt = "$msg_num $msgs{$msg_num}"; print "HTTP/1.0 $msg_txt\n"; }
    and here's the file I'm passing to server.pl:
    > more badreq.txt GET /index.html HTTP/1.1
    and here's my output:
    > ./server.pl badreq2.txt HTTP/1.0 400 HTTP/1.0 400 Bad Request
    What do you think? Thanks!
      Ahh.. the problem is that your hash isn't defined yet when you call msg in the if statement, but it is defined when you call it later on in the program. %msgs has to be defined before any calls to msg()
Re: calling a sub from an if statement
by qi3ber (Scribe) on Jul 25, 2000 at 21:32 UTC
    Another alternative would be to move the hash definition into the function definition, since that is the only place where that is used. This satisfies both the desire to modularize your code, and the need to the have the hash defined everwhere that the msg function is called. In this case the definition for your msg subroutine would be:

    sub msg { my $msgs = { '200' => 'OK', '400' => 'Bad Request', '403' => 'Forbidden', '404' => 'Not Found', '500' => 'Internal Sever Error', }; my $msg_num = $_[0]; my $msg_txt = "$msg_num $msgs{$msg_num}"; print "HTTP/1.0 $msg_txt\n"; }
Re: calling a sub from an if statement
by cburns (Novice) on Jul 23, 2000 at 23:53 UTC
    oops, didn't refresh to see the second response before my last post.
    let me give that a try...