3

I have a webserver successfully running on an Arduino Mega, but I'm trying to make sure it's parsing the GET requests in the fastest way possible. This is what the relevant code for the GET requests looks like:

String readString;
EthernetClient client = server.available();
if (client) {
  while (client.connected()) {   
    if (client.available()) {
      char c = client.read();

    if (readString.length() < 20) {
      readString += c;
    }
    ...
    ...
      if (readString.indexOf("?a1o") >0){
        mySwitch.send(a1o, 24);
      }
      if (readString.indexOf("?a1c") >0){
        mySwitch.send(a1c, 24);
      }

The sketch checks for 40 possible GET variables. I'm running it as a local webserver with the address: http://192.168.0.180.

-Is there a command faster than "indexOf" that I could be using?

-I saw some information suggesting that sometimes "parseInt" can be faster. Is there any way for me to make that work even though my local base URL is already full of integers? I would be willing to convert the commands into integers (like "11" instead of "a1o") if there was a way to use parseInt or another faster command with it.

Thanks!

  • Can you post an example of the sort of input you are processing? Is it, perhaps: http://192.168.0.180?a1o or http://192.168.0.180?a1c, or might you have multiple of them on the GET line? – Nick Gammon Jul 12 '15 at 1:23
  • Maybe state what these 40 GET variables are. Is there a pattern? Eg. a1o, a1c, a2o, a2c, a3o, a3c ... ? – Nick Gammon Jul 12 '15 at 1:24
  • yes, that's exactly how it looks: 192.168.0.180?a1o – Jerry Jul 12 '15 at 1:47
  • There's a pattern, but it doesn't have to be that way. Currently it goes: a1o, a1c, a2o, a2c, etc until a5o, a5c. Then it starts: b1o, b1c, etc. ('o' stands for 'open' and 'c' stands for 'close', it's opening and closing different wireless outlets). I would have no problem though renaming them all, like just giving them all numbers or a clear pattern, if there was a way to make it parse faster. – Jerry Jul 12 '15 at 1:49
  • Extract the query string once, and store it in a variable. Next test whether this variable is 'a1o' or 'a1c', etc. Using switch-case would be faster than a whole list of if statements. – Gerben Jul 12 '15 at 12:09
8

Well, for fast parsing I wouldn't use String for a start. I've written an example (below) which does away with the String class, and also demonstrates parsing the line once.

#include <SPI.h>
#include <Ethernet.h>

// Enter a MAC address and IP address for your controller below.
byte mac[] = {  0xB3, 0x8D, 0x72, 0x1D, 0xCE, 0x91 };

// Our IP address
IPAddress ip(10,0,0,241);

// Initialize the Ethernet server library
// with the IP address and port you want to use
// (port 80 is default for HTTP):
EthernetServer server(80);

void setup() 
  {
  // Open serial communications and wait for port to open:
  Serial.begin(115200);
  while (!Serial) { } // wait for serial port to connect.

  // start the Ethernet connection and the server:
  Ethernet.begin(mac, ip);
  server.begin();
  Serial.print(F("Server is at "));
  Serial.println(Ethernet.localIP());
  }  // end of setup

// how much serial data we expect before a newline
const unsigned int MAX_INPUT = 100;
// the maximum length of paramters we accept
const int MAX_PARAM = 10;

// Example GET line: GET /?foo=bar HTTP/1.1
void processGet (const char * data)
  {
  // find where the parameters start
  const char * paramsPos = strchr (data, '?');
  if (paramsPos == NULL)
    return;  // no parameters
  // find the trailing space
  const char * spacePos = strchr (paramsPos, ' ');
  if (spacePos == NULL)
    return;  // no space found
  // work out how long the parameters are
  int paramLength = spacePos - paramsPos - 1;
  // see if too long
  if (paramLength >= MAX_PARAM)
    return;  // too long for us
  // copy parameters into a buffer
  char param [MAX_PARAM];
  memcpy (param, paramsPos + 1, paramLength);  // skip the "?"
  param [paramLength] = 0;  // null terminator

  // do things depending on argument (GET parameters)

  if (strcmp (param, "foo") == 0)
    Serial.println (F("Activating foo"));
  else if (strcmp (param, "bar") == 0)
    Serial.println (F("Activating bar"));

  }  // end of processGet

// here to process incoming serial data after a terminator received
void processData (const char * data)
  {
  Serial.println (data);
  if (strlen (data) < 4)
    return;

  if (memcmp (data, "GET ", 4) == 0)
    processGet (&data [4]);
  }  // end of processData

bool processIncomingByte (const byte inByte)
  {
  static char input_line [MAX_INPUT];
  static unsigned int input_pos = 0;
  switch (inByte)
    {
    case '\n':   // end of text
      input_line [input_pos] = 0;  // terminating null byte
      if (input_pos == 0)
        return true;   // got blank line
      // terminator reached! process input_line here ...
      processData (input_line);
      // reset buffer for next time
      input_pos = 0;  
      break;

    case '\r':   // discard carriage return
      break;

    default:
      // keep adding if not full ... allow for terminating null byte
      if (input_pos < (MAX_INPUT - 1))
        input_line [input_pos++] = inByte;
      break;
    }  // end of switch
  return false;    // don't have a blank line yet
  } // end of processIncomingByte  


void loop() 
  {
  // listen for incoming clients
  EthernetClient client = server.available();
  if (client) 
    {
    Serial.println(F("Client connected"));
    // an http request ends with a blank line
    boolean done = false;
    while (client.connected() && !done) 
      {
      while (client.available () > 0 && !done)
        done = processIncomingByte (client.read ());
      }  // end of while client connected

    // send a standard http response header
    client.println(F("HTTP/1.1 200 OK"));
    client.println(F("Content-Type: text/html"));
    client.println(F("Connection: close"));  // close after completion of the response
    client.println();   // end of HTTP header
    client.println(F("<!DOCTYPE HTML>"));
    client.println(F("<html>"));
    client.println(F("<head>"));
    client.println(F("<title>Test page</title>"));
    client.println(F("</head>"));
    client.println(F("<body>"));
    client.println(F("<h1>My web page</h1>"));
    client.println(F("<p>Requested actions performed"));
    client.println(F("</body>"));
    client.println(F("</html>"));

    // give the web browser time to receive the data
    delay(10);
    // close the connection:
    client.stop();
    Serial.println(F("Client disconnected"));
  }  // end of got a new client
}  // end of loop

The incoming lines from the client are captured in a static buffer. This eliminates issues with memory fragmentation (caused by using String).

processIncomingByte is called for each byte from the client. Once an entire line is assembled it calls processData. If that finds a "GET " line it calls processGet. That looks for the parameters on the GET line. The GET line looks like this:

GET /?bar HTTP/1.1

So we need to get between the "?" and the next space, to get the parameter ("bar" in this case). This can be accomplished by finding their positions with strchr.

Finally (as a simple example) I tested with strcmp for a direct match on various words. You could probably make that more efficient but I doubt it would matter much.

  • I just implemented these changes, and it's noticeably a TON faster!!!!! I'm so happy with this! Thanks a lot for the help! – Jerry Jul 13 '15 at 2:49
  • I converted all my GET parameters into integers so that I could use a switch-case. I converted "param" into an integer like this: param2 = atoi(param); and then I did "switch(param2);". Can you please tell me if this is a bad way of doing things? Thanks. – Jerry Jul 13 '15 at 4:02
  • That's fine, it will give you a slight speed increase. If you liked my answer please "accept" it by clicking the tick mark next to it. This lets other users know that the answer was helpful. Thanks! – Nick Gammon Jul 13 '15 at 8:34
  • Thanks! JSYK how much faster your code was: beforehand it took 2-3 seconds after clicking the web link before the wireless switches would get activated. Now it only takes 200-300 ms. This is very useful to me. – Jerry Jul 13 '15 at 11:28

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.