Connecting Arduino Programming and Networking With The Ethernet Shield - Bob Hammell
Connecting Arduino Programming and Networking With The Ethernet Shield - Bob Hammell
Table of Contents
Preface
Getting Started
Connecting the Ethernet Shield Establishing a network connection
Testing connections
Using SD Cards
Formatting and initializing SD cards Reading and writing from SD
cards Creating and removing directories
Arduino as a Web Client
Making HTTP GET and POST requests Scraping webpages
Handling timeouts Sending tweets
Arduino as a Web Server
Using a static IP address Port forwarding and dynamic DNS
Accepting incoming HTTP connections Serving files from the SD
card Creating a web-based UI
Using UDP and Socket Programming
Communicating over UDP Building a DNS server Implementing a
custom application protocol
Appendix A Hypertext Transfer Protocol HTTP/1.0
Appendix B DNS Implementation and Specification
Preface
At this point, the Arduino hardly needs any introduction. Its become a force of
nature inspiring, in its short lifetime, millions of people from all walks of life and
with varying levels of prior experience in electronics and computer programming.
Theres much you can do with this flexible development platform, and so much
amazing work has already been done. But where things really get interesting,
really get useful, is when you make projects that talk to each other and to the rest
of the world.
That hobbyist and beginner electronics hackers and makers can create
standalone devices which communicate with other machines on the local network
and across the Internet, and using the same Internet protocols as used by
desktop PCs, servers, and mobile devices, is certainly not insignificant.
Despite the emergence of new development boards, shields, and modules, the
Ethernet Shield remains a popular choice for Arduino projects. And its easy to
see why the section of this book that covers getting the shield up and running is
very thin. Unfortunately, making full use of this ingenious device is a little more
difficult than the first steps suggestAnd that brings me neatly to the subject of
protocols and the reason why I wrote this book.
Online Resources
ConnectingArduino.com is the companion website for this book; you can contact
me there if theres anything I can help you with or if you want to show off your
work. Ive also put all of the project sketches up there so that you can download
them, instead of typing them in. Itll be worth your while to visit the site regularly
any news, updates, and addendums will be posted there first.
To the best of my ability, I have verified the accuracy of all of the information in this
book, and tried to ensure that the code samples are robust enough for you to use
(while not being so full of optimized programming code and error-checking as to
make the code difficult to understand). However, things change and mistakes do
happen. You can help me to improve future editions, for the benefit of other
Arduino enthusiasts, by contacting me at the website if you find any errors,
inaccuracies, or places where information is confusing.
Meaning
Italic
Bold
Monospace
font
Colored text
Items shown with colored text are links to other pages in this
book.
Getting Started
The Arduino Ethernet Shield is an additional circuit board that fits on top of your
Arduino. It extends the Arduinos capabilities with circuitry to connect to a network
router, using a commonly-available RJ45 Ethernet cable. Your Arduino projects
can communicate with the world through this connection everything from
fetching information from the Internet and displaying it on a liquid crystal display
(LCD), to providing publically-accessible, web-based tools that can control motors
and other hardware.
More than just a hardware device that can consume content and accept
messages, the Ethernet Shield is your entry point into building things for the
Internet of Things devices that take an active role in talking to humans and other
machines over Internet protocols.
In This Chapter
Connecting the Shield
Establishing a Network Connection
Introducing Web Clients and Web Servers
The Ethernet Shield R3 can also be used with earlier Uno devices and the
Duemilanove. However, when using older Arduinos, four of the shields header
pins are left unconnected. You must ensure that none of these pins are allowed to
make contact with any of the Arduinos components, or each other.
Suitable options for this are:
Wrap the two left-most pins on the top row, and the two left-most pins on the
bottom row, in insulating tape.
Bend the two left-most pins on the top row, and the two left-most pins on the
bottom row, away from contact with the Arduino.
Once the shield is fitted securely on the Arduino, you can reconnect the power.
Caution: It is usually safe to connect and disconnect cables and wires
from the Ethernet shield while the Arduino is connected to its power
supply. However, to avoid any accidental damage to electronic
components, it is preferable to disconnect the power before doing so.
The connectors and key components of the Arduino Ethernet Shield R3 are
shown below:
It is possible to stack other shields on top of the Ethernet Shield, and to use most
of the Arduinos pins as usual. However, the Arduino talks to the Ethernet Shield
over SPI and when actually using the Ethernet Shield, the following pins are
unavailable for any other purpose:
Arduino Uno
Pin
Arduino Mega
Pin
Function
D4
D4
D10
D10
D11
D50
MOSI
D12
D51
MISO
D13
D52
SCK
D53
There are several methods of cabling the Ethernet Shield to your network. This
choice makes no difference to how Arduino sketches are programmed, and you
should simply choose the one that is most convenient for you and your
workspace.
1. Plug one end of a CAT5 or CAT6 Ethernet cable with RJ45 connectors into
the socket on the Ethernet Shield.
2. Plug the other end of the cable into an available Ethernet port on your router.
3. Plug the Arduino into a suitable power supply (if it is not connected already).
1. On the Apple menu, click System Preferences, and then click Sharing.
2. On the sidebar, click Internet Sharing1 , and choose the Internet connection
you want to share from the Share your connection from menu.
3. Select the checkbox labelled Built-in Ethernet.
4. Click Start.
If your PCs operating system warns you that it has detected an IP address
conflict, you may have to connect either your PC or the Arduino to the network
using a static IP address.
As it is unlikely that you will ever need to modify this address while the sketch is
running, you can also define the MAC address using a constant array:
const byte mac[] = { 0x00, 0xC3, 0xA2, 0xE6, 0x3D, 0x57 };
The method begin() in the Ethernet librarys Ethernet class attempts to connect to
the network using the details passed into it as arguments.
There are actually four forms of this method that you can use, depending on how
much information you want to specify:
void begin(uint8_t *mac, IPAddress ip)
void begin(uint8_t *mac, IPAddress ip, IPAddress dns)
void begin(uint8_t *mac, IPAddress ip, IPAddress dns, IPAddress gateway)
void begin(uint8_t *mac, IPAddress ip, IPAddress dns, IPAddress gateway, IPAddress subnet)
At a bare minimum, you must call begin() and pass a MAC address as an array of
bytes. If you declare the MAC address with the keyword const, you will need to
cast it to a pointer of uint8_t values. For example:
Ethernet.begin((uint8_t*)mac);
If you pass an IP address then the Ethernet Shield will make a network
connection using a static IP address. If you do not define an IP address then the
shield will obtain one from the router using DHCP. For more information about
static IP addresses, see Using a Static IP Address.
The remaining two, optional parameters are usually not needed except when
working with complicated networks. If you have to specify the gateway address
then you must use a static IP address. If you need to specify the subnet address
then you must provide all three of the other arguments, and connect to the
network using a static IP address.
begin() will return the value 1 if it connected successfully, and 0 if the connection
failed.
If the sketch fails to establish a connection then there are a few things to try:
Change the code to use a different MAC address.
Check that the link indicator (as described above) is solid green or blinking. If
there is no light then this indicates a problem with your wiring.
Check your wiring carefully. Replace the Ethernet cable (if possible), and try a
different connection method such as directly to your router.
When the sketch successfully establishes a network connection, it calls four
methods of the Ethernet class to retrieve the configuration settings that were
given by the router.
Method
Description
dnsServerIP()
gatewayIP()
localIP()
subnetMask()
The DNS server, gateway, and subnet mask details sent to the serial port should
match those used by other devices on your network.
To learn how to set the IP address and other network configuration parameters
manually, and related topics such as port forwarding and dynamic DNS, see
Arduino as a Web Server.
On Mac OS X:
1. On the dock, click Finder.
2. On the sidebar, click Applications.
3. Click Utilities, then double-click Network Utility.
4. On the Ping tab, in the box labelled Enter the network address to ping, type
the IP Address displayed in the serial port monitor of the Arduino IDE.
5. Click the Ping button.
Using SD Cards
The Arduino has quite a small amount of on-board storage and memory. On its
own, it cannot store enough information to serve a large web-based interface or
send many files to connected clients. And when acting as a client itself, many of
the files an Arduino project needs to download are too big to be held in memory.
The Arduino Ethernet Shield comes with a built-in Secure Digital (SD) card socket
that you can access using the SD library, SD.h. This library is supplied with the
Arduino integrated development environment (IDE), and supports FAT16 and
FAT32 file systems on standard SD cards and high-capacity SDHC cards.
SD.h is a wrapper that simplifies access to the SD card. It uses another library,
SDFat.h, which is not covered in this book. SDFat.h is much more complicated,
containing many methods and data structures for working with the SD card at a
low-level, and it may be interesting to readers who are already experienced with
SD cards.
In This Chapter
Formatting and Initializing SD Cards
Reading from SD Cards
Writing to SD Cards
On Mac OS X:
1. Insert the SD card into a suitable card socket or USB card reader.
2. On the dock, click Finder.
3. On the sidebar, click Applications.
4. Click Utilities, and then double-click Disk Utility.
5. In the left panel, click the SD card (usually labelled NO NAME if the card
was not formatted with a volume name).
6. On the Erase tab, from the Volume Format list, click MS-DOS File System
or MS-DOS(FAT).2
7. Click Erase.
You do not need to include the Ethernet library if you are only using the SD card
socket and not actually connecting to a network.
The next step is to initialize the card using SD.begin(4). This method accepts one
argument and that is the pin number for the slave select function. Multiple SPI
devices can share most of the SPI connections wires, but each device must have
its own slave select wire. On the official Arduino Ethernet Shield, slave select for
the SD card is digital pin 4.
If you are using an Ethernet Shield clone from a different manufacturer, the slave
select function might be a different pin. You will need to locate this pin and change
the code accordingly.
SD.begin()is a simplified version of the initialization process, and it not only
initializes the SD card but also the FAT file system. If there is an error in either of
those stages then the function returns false.
#include <SPI.h>
#include <SD.h>
void setup() {
Serial.begin(9600);
while (!Serial);
//D53 on an Arduino Mega must be an output.
pinMode(53, OUTPUT);
Serial.print("Initializing SD card ");
if (!SD.begin(4)) {
Serial.println("FAILED!");
}
else {
Serial.println("OK!");
}
}
void loop() {
}
There are a number of reasons why card initialization can fail including if there is
not a card in the socket. If the SD card works on a PC then it generally safe to
assume that there is something in the file system that the Arduino SD library
cannot support. Try reformatting the SD card as described above.
Tip: Initializing the FAT file system can sometimes fail if there are no files
on the card. Try reformatting the card and then transferring a file to it from
your PC. Any file should be fine, provided that it is not 0 bytes long.
Remember to eject the SD card from your PC before removing it.
The open() method returns an instance of the File class. This contains methods
for reading and writing files on the SD card, and other methods that can be used
for reading information about those files.
It is very important that you close files and directories when the code does not
need them anymore. To close a file or directory, call the method close() of the
open files instance:
fp.close();
The File class can also open files and directories without you specifying the items
name using openNextFile() by finding the next item based on the file and
directory entries in the file system.
You can use this, and other methods from the File class, to write code that
displays a file list similar to a command line dir or ls operation. The methods of the
File class that will be used are:
Method
Description
name()
openNextFile()
isDirectory()
size()
close()
Closes a file.
More information and examples of how to list files can be found in Arduino as a
Web Server.
#include <SPI.h>
#include <SD.h>
void setup() {
Serial.begin(9600);
while (!Serial);
//D53 on an Arduino Mega must be an output.
pinMode(53, OUTPUT);
Serial.print("Initializing SD card ");
if (!SD.begin(4)) {
Serial.println("FAILED!");
}
else {
Serial.println("OK!");
File root = SD.open("/");
Serial.println(root.name());
File lsf;
while ((lsf = root.openNextFile())) {
if (lsf.isDirectory()) {
Serial.print("<DIR> ");
}
else {
char fsize[13];
sprintf(fsize, "%10d ", lsf.size());
Serial.print(fsize);
}
Serial.println(lsf.name());
lsf.close();
}
root.close();
}
}
void loop() {
}
Tip: The library used to process the FAT file system only supports file
names in the 8.3 format eight characters for the name, a period, and
then three characters for the file extension. It automatically converts file
names that do not follow this format.
Once you have opened a file with SD.open(), there are two methods of the File
class that you can use to read bytes from the file: read() and peek().
If you do not pass any arguments, a call to read() returns the next byte from the
file, and advances your position in the file. The method available() returns the
number of bytes left in the file that you have not yet read, and this can be used to
detect the end of the file. Once your position reaches the end of the file, there are
no bytes available.
To read the entire file from the SD card, you can loop until available() returns zero,
reading and printing bytes one-by-one:
#include <SPI.h>
#include <SD.h>
void setup() {
Serial.begin(9600);
while (!Serial);
//D53 on an Arduino Mega must be an output.
pinMode(53, OUTPUT);
Serial.print("Initializing SD card ");
if (!SD.begin(4)) {
Serial.println("FAILED!");
}
else {
Serial.println("OK!");
Serial.print("Checking for readme file ");
if (SD.exists("README.TXT")) {
Serial.println("OK!");
Serial.println();
File rm = SD.open("README.TXT");
while (rm.available() > 0) {
Serial.write(rm.read());
}
rm.close();
Serial.println();
}
else {
Serial.println("NOT FOUND!");
}
}
}
void loop() {
}
Peek() can be useful occasionally, but it is not used by the examples in this book.
It reads the next byte from the file in the same way as read(), but does not
advance your position in the file.
An alternate form of read() is used to read multiple bytes into an area of memory
often called a buffer including the memory occupied by an array or a struct, or
dynamically-allocated with malloc(). When used in this way, read() accepts two
arguments and returns an integer value indicating how many bytes were read
from the file:
int read(void *buf, uint16_t nbyte);
nbyte specifies the number of bytes that should be read from the file.
When working with arrays, keep the size of the data types in mind. An array of
integer types defined as int buf[100] allocates memory that is 200 bytes long, as
integer types on the Arduino are 16-bit values. The actual size in bytes of any type
(including arrays and structs) can be calculated using the sizeof() operator.
After reading, the read() method moves your position in the file forward based on
the number of bytes that it actually read from the file.
Description
position()
seek()
seek() accepts one argument and that is a 32-bit integer ranging from zero (the
start) to the size of the file (the end). This argument represents the absolute
position to move to. To step backwards two bytes from the current position, use a
call like:
fp.seek(fp.position() 2);
To step forwards two bytes from the current position, you can use:
fp.seek(fp.position() + 2);
Using seek() frequently will significantly increase how long it takes for your sketch
to process files. Using larger buffers will help, but you should always try to
balance how fast you need the sketch to run against how much memory it uses.
Writing to SD Cards
To write to a file on the SD card, you open it using a different mode. But the other
aspects of initializing the card are the same as described in Reading from SD
Cards.
When opening files, the default action is to open them in read-only mode. An
optional second argument to SD.open()sets the mode to one of the following
values:
Mode
Description
FILE_READ
FILE_WRITE
Opens a file for writing. If the file does not exist, it will be
created.
When a file is opened with FILE_WRITE and it already exists, it is opened at the
end so that any data that you write is appended to the existing content. If you
want to overwrite a file then the easiest way is usually to delete the existing file
before the call to SD.open().
The instance of the File class that is returned by a successful call to SD.open()
can be used to write to the file, using the method write(). Like read(), write() also
has two forms the first accepts a single byte argument, and this byte is written to
the file in the current position. The second form accepts a pointer to the area of
memory that contains the data to be written to the file, and an integer number
specifying how many bytes are to be written. The name of an array is actually a
pointer and so this can be used when calling write().
There is no guarantee that the data will be written to the file immediately it may
only be saved when the file is closed. To ensure that the data is written, you can
call the method flush(), which writes any information that is still held in the
Ethernet Shields buffers.
The following code sample creates a new file, and then writes the number 65 to it.
When the file is opened with a text editor, such as Notepad, TextEdit, or Vi, this
number appears as the ASCII character A. The code then writes an array of
numbers, which appears in a text editor as BCDEF.
Finally, this sample demonstrates how calls to write() can be used to store entire
structures with one call.
#include <SPI.h>
#include <SD.h>
struct TS {
byte G;
byte H;
byte I;
};
void setup() {
Serial.begin(9600);
while (!Serial);
//D53 on an Arduino Mega must be an output.
pinMode(53, OUTPUT);
Serial.print("Initializing SD card ");
if (!SD.begin(4)) {
Serial.println("FAILED!");
}
else {
Serial.println("OK!");
if (SD.exists("TEST.TXT"))
SD.remove("TEST.TXT");
Serial.print("Opening file for writing ");
File test = SD.open("TEST.TXT", FILE_WRITE);
if (!test) {
Serial.println("FAILED!");
return;
}
Serial.println("OK!");
// Write a byte
test.write(65);
// Declare an array and then write it to the file
byte buf[5] = { 66, 67, 68, 69, 70};
test.write(buf, 5);
// Create a struct and then write it to the file
TS myTest = {71, 72, 73};
test.write((uint8_t*)&myTest, sizeof(TS));
// Close the file
test.close();
Serial.println("Finished!");
}
}
void loop() {
}
Deleting Files
Deleting a file is a form of writing, during which entries are written to the FAT file
system indicating that a file has been deleted and that the areas of the SD card
that it occupies can be overwritten.
To delete a file, use the method SD.remove() and pass the file name (with file path
if needed) as a string argument:
SD.remove("DELETEME.TXT");
SD.mkdir("DIR_NAME");
If any intermediate directories do not exist, this method will create them too. For
example, passing the string TEMP1/TEMP2 to SD.mkdir() will create two new
directories the first being TEMP1, and the second being TEMP2, which is
created inside TEMP1.
To delete a directory, which also deletes any files and subdirectories that it
contains, use the method SD.rmdir():
SD.rmdir("DIR_NAME");
The available options will depend on which version of Mac OS X you are running.
If you would like more information about initializing the network and using the test
sketch, see Establishing a Network Connection. The code above is essentially the
same as used in Chapter 1 the only notable difference is that a light-emitting
diode (LED) is connected to the Arduino on digital pin 2 (through a 220 resistor)
and this is used as a visual alarm if there is a problem making a connection to the
network.
If the sketch cannot make a connection to the network, the while(true)loop flashes
the LED until the Arduino is reset. This prevents the sketch from progressing to
the loop() function.
Tip: Remember that digital pin 13 is used by the Ethernet shield and so
the Arduinos built-in LED on that pin cannot be used.
And then define two character arrays that specify the domain name of the web
server you wish to connect to, and the file that you want to download:
char wServer[] = "www.arduino.cc";
char wFile[] = "/";
These two strings form part of a web address, a universal resource locator (URL).
The full URL when accessed from a regular web browser on your PC is
http://www.arduino.cc/
The call to delay() is to ensure that this project waits for around 10 mins before it
makes another request to the server.
Two methods of the EthernetClient class are introduced in this code: connect()
and stop().
The call to connect() opens a connection to a machine on the Internet (or local
area network). The example code passes two arguments into the method: the first
of these is the machines domain name as an array of characters. The second
argument is the port number (usually 80 for HTTP). If the call was successful then
connect() returns the value 1; if it returns anything else then there has been an
error.
There are many reasons that calls to connect() can fail. If this happens:
1. Check that you can connect to your router using the sketch shown in
Establishing a Network Connection.
2. Ensure you have typed the server name correctly in the sketch.
3. See if the webpage can be accessed using a web browser on your PC.
4. Reset the sketch or wait until the delay() expires and the code attempts
another connection. Some problems are only temporary.
Once you have finished working with a connection, you should always close it as
the Ethernet Shield can only support four simultaneous connections. To close the
connection, call the method stop() from the active instance of the EthernetClient
class.
In this project, you set the servers domain name using a character array. When
the connect() method is called, it translates this domain name into an IP address
for you. Earlier versions of the Arduino Ethernet library did not do this.
If you want to connect by specifying an IP address instead, you can declare an
instance of the IPAddress class and pass this into connect() instead of the
character array:
IPAddress ip(174,129,243,245);
The line breaks above are intentional and are encoded in HTTP request and
response messages as two-byte sequences ASCII character 13 (carriage
return) followed by ASCII character 10 (line feed).
The first line is called the request line. After the keyword GET, there is the file
name and file path of the information that you are requesting. In this case, the
variable wFile is inserted and this currently contains the value /.
On the same line, the characters HTTP/1.0 show the version of the HTTP
protocol2 that the request conforms to, and the version that the server should
use when responding.
Host and Connection are two header fields that you can choose to send. These
are like arguments passed into a function they specify additional information that
the web server can use to fulfil the request.
Host simply restates the domain name of the server that you are contacting
some servers host many websites and need you to put the website that you are
wanting to talk to in the HTTP request. Connection specifies the type of HTTP
connection to be used. In this case, close tells the web server to terminate the
connection once it has responded to the request. Neither Host nor Connection are
actually part of the HTTP/1.0 protocol, they are from HTTP/1.1. However,
communication with many web servers relies on them being included. You can
find descriptions of the acceptable headers in HTTP/1.0 in the appendix, section
5.2 Request Header Fields.
Send information to the server using the print(), println(), or write() methods of the
EthernetClient class. These methods work in the same way as their counterparts
used to send data to Arduinos serial port. To generate and send the HTTP
request shown earlier in this section, insert the following code between the lines
Serial.println(OK!) and myClient.stop()in the sketchs loop() function:
myClient.print("GET ");
myClient.print(wFile);
myClient.println(" HTTP/1.0");
myClient.print("Host: ");
myClient.println(wServer);
myClient.println("Connection: close");
myClient.println();
The empty line sent to the server at the end of this code tells the web server that
you have finished sending the HTTP request and that it should now respond.
There is no observable difference if you run the sketch at this point. The current
code in the sketchs loop() closes the connection without waiting for the web
servers response.
If you are not familiar with working with the Arduino Ethernet Shields built-in SD
card socket and the SD.h library, you may wish to read Using SD Cards.
Three new methods of the EthernetClient class are included in the code above:
Method
Description
connected()
available()
read()
After creating a new file to hold the data, the code enters a while loop that waits
until the server drops the HTTP connection. Since there is no guarantee that all of
the data will be sent immediately, this loop waits for available data and then writes
it to the SD card when it does arrive.
Using HTTP/1.0, the server should end the connection once it is finished sending
its response. Your HTTP request includes the field Connection: close to help
ensure that this happens when communicating with HTTP/1.1 servers. However,
one of the most common causes of problems is web servers not closing
connections. Downloading files from the Internet through the Ethernet Shield can
be slow, but if the LED remains lit for a very long period of time then it may mean
that the server has not closed the connection.
In the next project, Project 2 Scraping Webpages to Retrieve Information, you
will see how to implement timeouts so that the sketch can recover if the server
stops sending information but does not close the connection. For now, if you
encounter this problem, try downloading a different file from a different web server.
Once the Arduino has downloaded the file and the LED is turned off, you can turn
off the Arduino and remove the SD card. Insert the SD card into your PC and
open up the file DOWNLOAD.TXT in a text editor such as Notepad, TextEdit, or
Vi. The file contains HTML code for the webpage you requested, but with several
lines of information before that.
Beginning with HTTP/1.1 and ending with the blank line, this is the servers
HTTP response header and it is not a part of the webpage. The first line of this
response is the most important, and it is called the status line.
Even though your code requests that the server use HTTP/1.0, this particular web
server responds with HTTP/1.1. However, it does not use any of the HTTP/1.1
features that could make it difficult for you to process the response.
After the protocol version, the server sends an HTTP status code (200) and a
reason phrase that describes the code. A full list of HTTP status codes is shown in
Appendix A, section 9. Status Code Definitions. However, for the purpose of
checking that the HTTP request completes successfully, there are only a few
status codes that you need to be aware of:
HTTP
Status
Code
Description
200
400
499
500
599
Regardless of the status code, most web servers return an HTML page after the
HTTP response header. This page may not be the file you requested and, instead,
may be a page that describes the error that has occurred.
In this project you will only modify the sketch so that it ignores the HTTP header
and does not write it to the file. This means that the file saved to the SD card
could be an error page.
Tip: You can use a web-based tool such as Rex Swains HTTP Viewer
(http://www.rexswain.com/httpview.html) to verify how a web server
responds to an HTTP/1.0 request.
Before the while(myClient.connected()) loop, add the following lines:
char lc;
while (myClient.connected()) {
if (myClient.available()) {
char nc = myClient.read();
if ((lc == 10) && (nc == 13)) {
while (myClient.available() == 0);
myClient.read();
break;
}
else
lc = nc;
}
}
This is not pretty code, but it works. The loop skips characters from the servers
response until it finds a line feed (10) followed by a carriage return (13) it
matches the middle part of the sequence that is used to mark the end of the HTTP
response header. The code then skips the next character (the final line feed) and
terminates the loop.
The sketch can now write the remaining characters from the servers response to
the SD card. In this project, the LED is lit while it writes the file so that you know
not to remove the card in the middle of the process.
Source Code
The full source code for the file-downloading sketch is shown below.
This sketch has been changed to save something more useful than the Arduino
homepage to the SD card. Since downloading this file too often is not especially
useful, the delay in this sketch has been extended to wait for approximately 1
hour.
#include <SPI.h>
#include <Ethernet.h>
#include <SD.h>
byte mac[] = { 0x00, 0xC3, 0xA2, 0xE6, 0x3D, 0x57 };
byte LED = 2;
char wServer[] = "media.wizards.com";
char wFile[] = "/images/magic/tcg/resources/rules/MagicCompRules_20140601.pdf";
EthernetClient myClient;
void setup() {
//D53 on an Arduino Mega must be an output.
pinMode(53, OUTPUT);
pinMode(LED, OUTPUT);
Serial.begin(9600);
while (!Serial);
Serial.print("Establishing network connection ");
if (Ethernet.begin(mac) == 0) {
Serial.println("FAILED!");
while (true) {
digitalWrite(LED, HIGH);
delay(500);
digitalWrite(LED, LOW);
delay(500);
}
}
else
Serial.println("OK!");
}
void loop() {
Serial.print("Connecting to ");
Serial.print(wServer);
Serial.print("... ");
if (myClient.connect(wServer, 80) == 1) {
Serial.println("OK!");
myClient.print("GET ");
myClient.print(wFile);
myClient.println(" HTTP/1.0");
myClient.print("Host: ");
myClient.println(wServer);
myClient.println("Connection: close");
myClient.println();
if (SD.begin(4)) {
if (SD.exists("MAGIC.PDF"))
SD.remove("MAGIC.PDF");
Serial.print("Saving response ");
digitalWrite(LED, HIGH);
File dd = SD.open("MAGIC.PDF", FILE_WRITE);
char lc;
while (myClient.connected()) {
if (myClient.available()) {
char nc = myClient.read();
if ((lc == 10) && (nc == 13)) {
while (myClient.available() == 0);
myClient.read();
break;
}
else
lc = nc;
}
}
while (myClient.connected()) {
if (myClient.available() > 0) {
dd.write(myClient.read());
}
}
dd.close();
delay(500);
Serial.println("OK");
digitalWrite(LED, LOW);
}
else
Serial.println("No SD card detected!");
myClient.stop();
}
else
Serial.println("FAILED!");
delay(3600000);
asm volatile(" jmp 0");
}
To begin, start a new Arduino sketch and paste in the code below, or type it
carefully. This is your starting point, and is based on the file-downloading sketch
covered in Project 1 Setting up a Basic Web Client. In its current state, the
sketch connects to the network and sends an HTTP request to the server to fetch
the webpage.
To keep the code a little tidier, this example places the code that fetches the
HTML in a new function, and calls this function from the sketchs loop() function. It
writes the HTML response from the server to the serial port, which you can view
by using the serial port monitor in the Arduino integrated development
environment (IDE).
The code to ignore the HTTP response header is now also contained in its own
function skipHeader().
#include <SPI.h>
#include <Ethernet.h>
const byte mac[] = { 0x00, 0xBC, 0xA2, 0xE6, 0x3D, 0x57 };
const byte LED = 2;
const char wServer[] = "www.gamestop.com";
const char wFile[] = "/xbox-360/games/naruto-shippuden-ultimate-ninja-storm-3-full-burst/110541";
EthernetClient myClient;
void setup() {
//D53 on an Arduino Mega must be an output.
pinMode(53, OUTPUT);
pinMode(LED, OUTPUT);
Serial.begin(9600);
while (!Serial);
Serial.print("Establishing network connection ");
if (Ethernet.begin((uint8_t*)mac) == 0) {
Serial.println("FAILED!");
while (true) {
digitalWrite(LED, HIGH);
delay(500);
digitalWrite(LED, LOW);
delay(500);
}
}
else {
Serial.println("OK!");
}
}
void loop() {
if (getHTML())
delay(600000);
else
delay(60000);
}
void skipHeader() {
char lc;
while (myClient.connected()) {
if (myClient.available()) {
char nc = myClient.read();
if ((lc == 10) && (nc == 13)) {
while (myClient.available() == 0);
myClient.read();
break;
}
else
lc = nc;
}
}
}
boolean getHTML() {
Serial.print("Connecting to ");
Serial.print(wServer);
Serial.print("... ");
if (myClient.connect(wServer, 80) != 1) {
Serial.println("FAILED!");
return false;
}
Serial.println("OK!");
myClient.print("GET ");
myClient.print(wFile);
myClient.println(" HTTP/1.0");
myClient.print("Host: ");
myClient.println(wServer);
myClient.println("User-Agent: Mozilla/5.0 (Windows NT 6.3; WOW64; Trident/7.0; Touch: MAARJS; rv:11.0) like
myClient.println("Connection: close");
myClient.println("Cookie: user_country=USA");
myClient.println();
skipHeader();
while (myClient.connected()) {
if (myClient.available() > 0) {
Serial.write(myClient.read());
}
}
myClient.stop();
return true;
}
The HTTP request also sends a fake cookie, by adding the Cookie field to send a
cookie called user_country with the value USA.
And finally, extract the three-digit status code and convert it to an integer. The
entries buf[9], buf[10], and buf[11] contain the three digits of the status code.
There are various ways of converting these three characters into a number. An
example of such a function is shown here:
int getStatusCode(char sc1, char sc2, char sc3) {
String tmp;
tmp.concat(sc1);
tmp.concat(sc2);
tmp.concat(sc3);
return tmp.toInt();
}
Add the getStatusCode() function to the sketch and then, after the statement
myClient.read((uint8_t*)buf, 12); add the line:
int sc = getStatusCode(buf[9], buf[10], buf[11]);
At this point, the sketch should check that the HTTP response status code is 200.
Status codes in the range 300 through 308 are usually some kind of redirection. A
web browser should, ideally, read through the remaining the fields in the HTTP
header to find the new destination.
However, in this project, you only perform basic checks to see if the request
completed successfully. If the status code is 300499 then the sketch enters a
while loop that flashes the LED and takes no further action. These error codes
include files not being available at the specified location and errors that are
caused by an invalid request to the server. It is unlikely that these problems will be
fixed without making changes to the sketch and re-uploading it to the Arduino.
If the status code is 500599 then the error might be temporary, and so the sketch
is programmed to try fetching the webpage again a little later.
Only if the status code is 200 does the getHTML() function continue to read the
data and process it. Since this project does not make any further use of the HTTP
response header, make a call to skipHeader() to move past the header and up to
the start of the HTML content.
At this stage, the getHTML() function should look like this:
boolean getHTML() {
Serial.print("Connecting to ");
Serial.print(wServer);
Serial.print("... ");
if (myClient.connect(wServer, 80) != 1) {
Serial.println("FAILED!");
return false;
}
Serial.println("OK!");
myClient.print("GET ");
myClient.print(wFile);
myClient.println(" HTTP/1.0");
myClient.print("Host: ");
myClient.println(wServer);
myClient.println("User-Agent: Mozilla/5.0 (Windows NT 6.3; WOW64; Trident/7.0; Touch: MAARJS; rv:11.0) like
myClient.println("Connection: close");
myClient.println("Cookie: user_country=USA");
myClient.println();
while (myClient.available() < 12);
char buf[12];
myClient.read((uint8_t*)buf, 12);
int sc = getStatusCode(buf[9], buf[10], buf[11]);
if ( (sc >= 300) && (sc <= 499)) {
myClient.stop();
while (true) {
digitalWrite(LED, LOW);
delay(500);
digitalWrite(LED, HIGH);
delay(500);
}
}
else if ( (sc >= 500) && (sc <= 599)) {
myClient.stop();
return false;
}
// Will only reach here if status codes 200-226
// are received.
if (sc == 200) {
skipHeader();
Serial.println("OK! Looking for price");
}
myClient.stop();
return true;
}
You can run the sketch. If all is well, the message OK! Looking for price is sent
to the serial port monitor.
The Arduino sketch can find the pre-owned price by waiting until it encounters the
sequence of characters var CI_PreOwnedPrice = and then reading until it finds
the next apostrophe. In this sketch, the marker sequence is defined in the variable
mrkPrice. The characters between the marker sequence and the next apostrophe
are the price.
The process for extracting this value uses two String objects as temporary buffers.
There are faster ways, but using String objects has the advantage of being slightly
easier to understand for beginners.
Figure 8 shows the process that is implemented in the Arduino C code.
The equivalent Arduino C code for this process is constructed using two while
loops:
String buffer = "";
String pBuf = "";
int bPtr = 0;
while (myClient.connected()) {
if (myClient.available() > 0) {
buffer.concat((char)myClient.read());
bPtr++;
if (bPtr == 24) {
if (buffer == mrkPrice) {
while (myClient.connected()) {
if (myClient.available() > 0) {
char tmp = myClient.read();
if (tmp != '\'')
pBuf.concat(tmp);
else
break;
}
}
break;
}
buffer = buffer.substring(1);
bPtr = 23;
}
}
}
float newPrice = stringToFloat(pBuf);
Serial.print("Price: $");
Serial.println(newPrice);
You can find the source code for the function stringToFloat() in the listing at the
end of this project. If you are working with version 1.5 of the Arduino IDE (this
version is in beta testing at the time of writing) then the String class now includes
the method toFloat() that you can use instead.
Handling Timeouts
The GameStop.com server tends to work as expected when responding to
HTTP/1.0 clients, it closes the connection after it has sent its response. In
addition, this sketch will also work with servers that mistakenly keep the
connection open because it closes the connection to the server itself when it
receives an unwelcome status code or when the price has been extracted
successfully.
However, if the server returns the webpage successfully but the sketch cannot
find the marker used to extract the price, then it is possible for the sketch to
become stuck in a loop. If the server does not close the connection then the
sketch might continue to wait for data which will never arrive.
There are several ways of building a timeout feature that stops the loop if no data
is received for a long period of time. One way is to initialize an unsigned long
variable and decrement it every time available() returns zero. When this timeout
counter reaches zero, the sketch should break out of the loop.
Add a global unsigned long to the sketch:
unsigned long timeout;
In the function getHTML(), after declaring the two string buffers and the integer
bPtr, initialize the timeout counter to a suitably high number. For example:
timeout = 60000;
Add the following code at the end of the main while loop in getHTML() so that it
extends the statement if(myClient.available() > 0) {} with an else clause:
else {
timeout--;
if (timeout == 0)
break;
}
And then add the following code after the line Serial.println(newPrice):
if (itmPrice == 0)
itmPrice = newPrice;
else if (itmPrice > newPrice)
digitalWrite(LED, HIGH);
else
digitalWrite(LED, LOW);
When the sketch first runs, itmPrice is initialized as zero. The price on the website
will become the new benchmark. Since this also happens if the Arduino loses
power, you could expand this project to save the benchmark price to the SD card
and compare newly-retrieved prices against that instead.
Source Code
The complete source code for the price-monitoring sketch is shown below. When
you have verified that everything is functioning correctly, you can safely remove all
of the messages sent to the serial port and disconnect the Arduino from your PC.
If powered and cabled to the network, it should monitor the webpage for as long
as the Arduino is able to make a connection to the web server.
Tip: Arduinos can usually run for months without any problems. Do not
be worried about leaving the device running.
#include <SPI.h>
#include <Ethernet.h>
const byte mac[] = { 0x00, 0xB7, 0xA2, 0xE6, 0x3D, 0x57 };
const byte LED = 2;
const char wServer[] = "www.gamestop.com";
const char wFile[] = "/xbox-360/games/naruto-shippuden-ultimate-ninja-storm-3-full-burst/110541";
const String mrkPrice = "var CI_PreOwnedPrice = '";
float itmPrice = 0.0;
EthernetClient myClient;
unsigned long timeout;
void setup() {
//D53 on an Arduino Mega must be an output.
pinMode(53, OUTPUT);
pinMode(LED, OUTPUT);
Serial.begin(9600);
while (!Serial);
Serial.print("Establishing network connection ");
if (Ethernet.begin((uint8_t*)mac) == 0) {
Serial.println("FAILED!");
while (true) {
digitalWrite(LED, HIGH);
delay(500);
digitalWrite(LED, LOW);
delay(500);
}
}
else {
Serial.println("OK!");
}
}
void loop() {
if (getHTML())
delay(600000);
else
delay(60000);
}
void skipHeader() {
char lc;
while (myClient.connected()) {
if (myClient.available()) {
char nc = myClient.read();
if ((lc == 10) && (nc == 13)) {
while (myClient.available() == 0);
myClient.read();
break;
}
else
lc = nc;
}
}
}
boolean getHTML() {
Serial.print("Connecting to ");
Serial.print(wServer);
Serial.print("... ");
if (myClient.connect(wServer, 80) != 1) {
Serial.println("FAILED!");
return false;
}
Serial.println("OK!");
myClient.print("GET ");
myClient.print(wFile);
myClient.println(" HTTP/1.0");
myClient.print("Host: ");
myClient.println(wServer);
myClient.println("User-Agent: Mozilla/5.0 (Windows NT 6.3; WOW64; Trident/7.0; Touch: MAARJS; rv:11.0) like
myClient.println("Connection: close");
myClient.println("Cookie: user_country=USA");
myClient.println();
while (myClient.available() < 12);
char buf[12];
myClient.read((uint8_t*)buf, 12);
int sc = getStatusCode(buf[9], buf[10], buf[11]);
if ( (sc >= 300) && (sc <= 499)) {
myClient.stop();
while (true) {
digitalWrite(LED, HIGH);
delay(500);
digitalWrite(LED, LOW);
delay(500);
}
}
else if ( (sc >= 500) && (sc <= 599)) {
myClient.stop();
return false;
}
// Will only reach here if status codes 200-226
// are received.
if (sc == 200) {
skipHeader();
String buffer = "";
String pBuf = "";
int bPtr = 0;
while (myClient.connected()) {
if (myClient.available() > 0) {
timeout = 60000;
buffer.concat((char)myClient.read());
bPtr++;
if (bPtr == 24) {
if (buffer == mrkPrice) {
while (myClient.connected()) {
if (myClient.available() > 0) {
char tmp = myClient.read();
if (tmp != '\'')
pBuf.concat(tmp);
else
break;
}
}
break;
}
buffer = buffer.substring(1);
bPtr = 23;
}
}
else {
timeout--;
if (timeout == 0)
break;
}
}
float newPrice = stringToFloat(pBuf);
Serial.print("Price: $");
Serial.println(newPrice);
if (itmPrice == 0)
itmPrice = newPrice;
else if (itmPrice > newPrice)
digitalWrite(LED, HIGH);
else
digitalWrite(LED, LOW);
}
myClient.stop();
return true;
}
int getStatusCode(char sc1, char sc2, char sc3) {
String tmp;
tmp.concat(sc1);
tmp.concat(sc2);
tmp.concat(sc3);
return tmp.toInt();
}
float stringToFloat(String tmp) {
char floatbuffer[32];
tmp.toCharArray(floatbuffer, sizeof(floatbuffer));
return atof(floatbuffer);
}
post tweets, is possible but would require a large amount of work. Most examples
of tweeting from an Arduino, including this one, use a service that acts as an
intermediary between your sketch and Twitter.
ThingTweet is a Twitter app that acts as a kind of proxy server for simple devices.
You can send your tweets to ThingTweet and their system posts the status update
messages to Twitter for you. Sending information this way is much simpler than
dealing with Twitters API directly.
To use ThingTweet, you first need to sign up for a free account and authorize it to
connect to your Twitter account:
1. Create a free account at ThingSpeak by signing up at
https://thingspeak.com/users/sign_up
2. On the main navigation bar, click Apps.
3. Click ThingTweet, and then click Link Twitter Account.
4. Enter your Twitter username and password, then click Authorize App.
5. Click Back to ThingTweet.
6. Make a note of the API Key associated with the Twitter account.
Caution: Since ThingTweet is a Twitter app, you do not need to send your
Twitter username and password to it from the Arduino. However,
someone could intercept your API key and then post tweets to your
account. If this happens, you can change the API key from the
ThingSpeak.com website.
To send tweets from an Arduino sketch, you need to open a connection to the
ThingTweet server and make an HTTP request using the POST method.
Start a new sketch in the Arduino IDE and paste in the following code, or type it
carefully.
#include <SPI.h>
#include <Ethernet.h>
const byte mac[] = { 0x00, 0xC2, 0xA2, 0xE6, 0x3D, 0x57 };
const byte LED = 2;
const char wServer[] = "api.thingspeak.com";
const char wFile[] = "/apps/thingtweet/1/statuses/update";
EthernetClient myClient;
void setup() {
//D53 on an Arduino Mega must be an output.
pinMode(53, OUTPUT);
pinMode(LED, OUTPUT);
Serial.begin(9600);
while (!Serial);
Serial.print("Establishing network connection ");
if (Ethernet.begin((uint8_t*)mac) == 0) {
Serial.println("FAILED!");
while (true) {
digitalWrite(LED, HIGH);
delay(500);
digitalWrite(LED, LOW);
delay(500);
}
}
Serial.println("OK!");
}
void loop() {
}
void skipHeader() {
char lc;
while (myClient.connected()) {
if (myClient.available()) {
char nc = myClient.read();
if ((lc == 10) && (nc == 13)) {
while (myClient.available() == 0);
myClient.read();
break;
}
else
lc = nc;
}
}
}
You start with a basic sketch that connects to the network and defines two
constants that describe the web server the sketch is going to connect to.
The server name for ThingTweet is api.thingspeak.com and, to send a tweet, you
make an HTTP request for the page /apps/thingtweet/1/statuses/update.
You will need the API Key that you received when you linked ThingTweet to your
Twitter account. If you do not have it:
1. Sign in to ThingSpeak at www.thingspeak.com.
2. On the main navigation bar, click Apps.
3. Click ThingTweet.
4. Make a note of the API Key associated with the Twitter account.
Add the API Key to the Arduino sketch as a global array of characters:
const char API_Key[] = "THE16DIGITAPIKEY";
tweet() is not complete yet you will add code to make an HTTP POST to
ThingTweet in the next section.
At the end of the sketchs setup() function, add a call to tweet(). This shows
whether tweets can be sent successfully. For example:
Aside from api_key, ThingTweet passes the key/value pairs on to Twitter so you
can add any of the fields from the status update method in Twitters API:
Field
Description
status
in_reply_to_status_id
lat
long
place_id
display_coordinates
trim_user
include_entities
You can also include special commands such as direct messaging and retweets.
These commands are usually sent at the start of the status update message and,
although not used in this project, a list of these can be found at
https://support.twitter.com/articles/14020-twitter-for-sms-basic-features
Extend the tweet() function to send a POST request by adding the following the
code before the call to myClient.stop():
myClient.print("POST ");
myClient.print(wFile);
myClient.println(" HTTP/1.0");
myClient.print("Host: ");
myClient.println(wServer);
myClient.println("Connection: close");
myClient.println("Content-Type: application/x-www-form-urlencoded");
myClient.print("Content-Length: ");
myClient.println(message.length()+32);
myClient.println();
//POST some data
myClient.print("api_key=");
myClient.print(API_Key);
myClient.print("&status=");
myClient.println(message);
return result;
}
This function currently ignores the HTTP status code, which is not a
recommended course of action, but checking only the ThingTweet return code is
usually sufficient while testing.
When the mode pin of the SRF05 is left unconnected, the device operates in the
same way as the SRF04. On the SRF04, this pin would also be unconnected.
Reading from the Ping))) is slightly different to reading from the SRF04 and
SRF05. However, the example code in this sketch is designed to work with any of
the three devices.
Declare two constants at the top of the sketch: US_Trigger and US_Echo:
const byte US_Trigger = 3;
const byte US_Echo = 7;
Ultrasonic range finders typically require a short trigger pulse to trigger them this
is assigned to digital pin 3 on the Arduino.
The time that it takes for the signal to bounce back to the device is measured and
returned as a pulse on the echo line, which is assigned to the Arduinos digital pin
7.
The Parallax Ping))) uses trigger and echo signals on the same pin. In this sketch,
you can set US_Trigger and US_Echo to use the same digital pin in the
declaration of the constants.
Add this function to the sketch:
long getDistance() {
pinMode(US_Trigger, OUTPUT);
digitalWrite(US_Trigger, LOW);
delayMicroseconds(2);
digitalWrite(US_Trigger, HIGH);
delayMicroseconds(2);
digitalWrite(US_Trigger, LOW);
pinMode(US_Echo, INPUT);
long duration = pulseIn(US_Echo, HIGH);
return (duration / 58);
}
Depending on the size of your environment and the range of the ultrasonic sensor
you are using, the device might detect the walls of the room. To avoid sending
tweets unnecessarily, the MAX_RANGE constant specifies how close an object
has to be to count as detected.
Detected_Object is used to ensure that the same object is not detected
continuously, generating hundreds of tweets.
If you have not done so already, add a call to tweet() in the sketchs setup()
function to send a message stating that the Arduino is online and ready. Since
tweet() returns false if the message is not sent successfully to ThingTweet, you
can continually retry sending the message using a while loop:
String message = "I am online, and watching (";
message.concat(random(0, 0xFFFFul));
message.concat(")");
while (tweet(message) == false) {
delay(5000);
}
was an object in front of the sensor but it has now been removed. Create a
different message for this.
Finally, check if a message string was created, if it was, tweet it by passing the
message string as an argument to tweet().
Source Code
The complete sketch for this project is shown below. Remember to replace
API_Key with the ThingTweet API key for your own Twitter account.
#include <SPI.h>
#include <Ethernet.h>
const byte mac[] = { 0x00, 0xC2, 0xA2, 0xE6, 0x3D, 0x57 };
const byte LED = 2;
const byte US_Trigger = 3;
const byte US_Echo = 7;
const byte MAX_RANGE = 250;
boolean Detected_Object;
EthernetClient myClient;
const char wServer[] = "api.thingspeak.com";
const char wFile[] = "/apps/thingtweet/1/statuses/update";
const char API_Key[] = "THE16DIGITAPIKEY";
void setup() {
//D53 on an Arduino Mega must be an output.
pinMode(53, OUTPUT);
pinMode(LED, OUTPUT);
Serial.begin(9600);
while (!Serial);
Serial.print("Establishing network connection ");
if (Ethernet.begin((uint8_t*)mac) == 0) {
Serial.println("FAILED!");
while (true) {
digitalWrite(LED, HIGH);
delay(500);
digitalWrite(LED, LOW);
delay(500);
}
}
Serial.println("OK!");
randomSeed(analogRead(2));
String message = "I am online, and watching (";
message.concat(random(0, 0xFFFFul));
message.concat(")");
while (tweet(message) == false) {
delay(5000);
}
Detected_Object = false;
// Delay for 15s to allow time for humans to move out of the way.
delay(15000);
}
void loop() {
String message;
long distance = getDistance();
if (distance < MAX_RANGE) {
if (!Detected_Object) {
if (rc == '1')
result = true;
else
result = false;
myClient.stop();
digitalWrite(LED, LOW);
return result;
}
void skipHeader() {
char lc;
while (myClient.connected()) {
if (myClient.available()) {
char nc = myClient.read();
if ((lc == 10) && (nc == 13)) {
while (myClient.available() == 0);
myClient.read();
break;
}
else
lc = nc;
}
}
}
The current version of HTTP is version 1.1. However, HTTP/1.1 includes several features that make it
slightly more difficult for small web clients to deal with.
3
This user agent string is not really fake but it falsely identifies the Arduino as something else. For more
information about user agents, see 10.15 User-Agent.
Each of the four parts in an IP address is a number in the range 0 through 255.
The first three parts should be the same as other devices on your network, and
this is usually 192.168.0 for most home networks. The final part must be unique to
this device and, as most routers begin assigning IP addresses from 192.168.0.2
and work upwards, it is usually safest to pick a large number.
When called with an IP address, Ethernet.begin() does not return a value. To
check whether the connection has been established, you can make the call to
Ethernet.begin() and then verify that Ethernet.localIP(), Ethernet.gatewayIP(), and
Ethernet.subnetMask() return sensible values.
The starting point for this project is a sketch that makes a network connection
using the IP address declared near the top of the file, just after the library
inclusions and the declaration of the media access control (MAC) address. It then
creates an instance of the EthernetServer class, specifying 80 as the port it will
listen on. Finally, it calls the EthernetServer class method begin() to start the
server.
#include <SPI.h>
#include <Ethernet.h>
const byte mac[] = { 0x00, 0xC3, 0xA2, 0xE6, 0x3D, 0x57 };
const byte ip[] = { 192, 168, 0, 99 };
EthernetServer myServer(80);
void setup() {
//D53 on an Arduino Mega must be an output.
pinMode(53, OUTPUT);
Serial.begin(9600);
while (!Serial);
Serial.println("Establishing network connection");
Ethernet.begin((uint8_t*)mac, (uint8_t*)ip);
Serial.print("IP Address: ");
Serial.println(Ethernet.localIP());
Serial.print("Default Gateway: ");
Serial.println(Ethernet.gatewayIP());
Serial.print("Subnet Mask: ");
Serial.println(Ethernet.subnetMask());
Serial.print("DNS Server: ");
Serial.println(Ethernet.dnsServerIP());
myServer.begin();
}
void loop() {
}
To check that everything is working, ping the device using the instructions in
Testing the Connection. The sketch wont respond to web requests until you add
code to accept connections in the sketchs loop() function.
(TCP) port 80, and the IP address tells the client where it can find the web server.
However, when your computers connect to the Internet through a router, your
Internet service provider (ISP) assigns an additional IP address to the router. To
the outside world, all devices on your local network appear to have the same IP
address the one set by the ISP. The local network addresses that you have
been working with so far are not used by clients connecting from the Internet.
This creates two problems: how can your router know which machine on your
network is supposed to respond to web requests, and how do clients on the
Internet find your router?
Port forwarding is a configuration setting that you can use to tell your router which
device on your local area network (LAN) should receive the connection from the
outside world. Unfortunately, the exact method of setting this up is different for
each router.
As a general guide:
1. Login to your routers administration panel. For most home routers, this is
usually done by visiting the URL http://192.168.0.1 in a web browser on your
PC.
2. Look for an option or page that allows you to control inbound connections.
This may be named Port Forwarding, Firewall Rules, Services, or something
similar.
3. Create a rule that says the HTTP service (TCP:80) is allowed, and should be
sent to the LAN server/machine 192.168.0.99 (the IP address set in the
Arduino sketch).
When you visit a website, you use its domain name (for example, google.com) as
part of the web address. Your web browser looks up the domain name to find the
associated IP address, using the domain name system (DNS). Clients make
connections to servers using these IP addresses.
But it is unlikely that your ISP has allocated a fixed (or static) IP address to your
router. Instead, the IP address will change every time your router connects to the
Internet. If the connection is dropped, or the router restarts, this address will
change.
Dynamic DNS (DDNS) is a method of automatically updating DNS records when
the router receives a new IP address from an ISP. This means that any web
clients trying to find your server using a domain name will always be given the upto-date IP address.
Although there are many DDNS services out there, they all tend to work the same
way: when your router connects to the Internet then either it or a machine on the
LAN contacts the DDNS servers and tells them the new IP address. You can
usually choose whether to use a domain name given to you by the DDNS service,
or buy your own domain name and use that.
Accepting Connections
The method available() in the EthernetServer class returns an instance of
EthernetClient if a client is waiting to the connect to the web server. If no clients
are waiting, available() returns a result that evaluates to false.
This should be called in the sketchs loop() function so that the server is able to
keep responding to connections while the Arduino has power. Change the
sketchs loop() function to match the following code sample:
void loop() {
EthernetClient client = myServer.available();
if (client) {
Serial.println("Incoming connection");
client.stop();
}
}
This loop() accepts connection requests from clients, but then closes the
connection without sending any data to the client. It only sends a message to the
serial port so that you can see when incoming connections are detected. These
messages are viewable from the serial port monitor in the Arduino IDE.
Using a web browser on your PC, or a tool such as the W3Cs markup validation
service at validator.w3.org, you should be able to connect to the server and
receive the HTTP error 500.
To respond to the web client, you must send an HTTP response header followed
by data (if the request was successful). For example:
HTTP/1.0 200 OK[crlf]
Content-Type: text/plain[crlf]
Connection: close[crlf]
[crlf]
Connection received OK.
The sketch in this project does not check which version of HTTP the client wants
to use, it always sends back responses using HTTP/1.0. But most modern web
browsers are extremely tolerant when it comes to servers returning unexpected,
or partial, responses.
For testing purposes, add code to the loop() function to send the HTTP response
shown above:
void loop() {
EthernetClient client = myServer.available();
if (client) {
Serial.println("Incoming connection");
client.println("HTTP/1.0 200 OK");
client.println("Content-Type: text/plain");
client.println("Connection: close");
client.println();
client.println("Connection received OK.");
client.stop();
}
}
Machines on the local network can connect using the IP address sent to the serial
port during the sketchs setup() function. If your network router (and any DNS or
The Arduino uses an analog to digital converter (ADC) to measure the voltage
level of the input pin. When the LDR is exposed to light, its resistance decreases
and so the voltage read by the ADC is high. When light is blocked, the resistance
of the LDR increases and so the voltage read by the ADC is lower.
The 10K resistor and LDR form a potential divider, which protects the Arduino
from short circuits by ensuring that there is always some resistance on the line.
Add this function to the sketch:
int getAnalogReading() {
return analogRead(0);
}
Returning Webpages
Returning a proper webpage is not very different from returning the test message.
The process is:
1. Send back an HTTP response that tells the web browser that the request
completed successfully (HTTP status code 200).
2. Send the Content-Type header field with the value text/html.1
3. Send the HTML code for the webpage.
Create a new function in the sketch, name this sendWebpage().
void sendWebpage(EthernetClient client) {
}
Add these lines to the functions body to send an HTTP response header which
indicates that the request was successful and that the browser should expect an
HTML file:
client.println("HTTP/1.0 200 OK");
client.println("Content-Type: text/html");
client.println("Connection: close");
client.println();
The remainder of the function sends the HTML page in three parts. To do this:
1. Make calls to client.print() to send HTML code until the value of the sensor is
needed.
2. Make a call to client.print() and pass the value returned by the function
getAnalogReading().
3. Make calls to client.print() and send the remaining HTML code for the
document.
The full source code for sendWebpage() is at the end of this project. You can copy
it into your sketch now if you are unsure how to write the HTML code.In the
sketchs loop() function, replace the calls to client.println() that send back the test
message with a call to the sendWebpage() function. loop() should look this:
EthernetClient client = myServer.available();
if (client) {
Serial.println("Incoming connection");
sendWebpage(client);
client.stop();
}
On your PC, open your web browser and type http://192.168.0.99/ (or whatever IP
address you set in the sketch) into the address bar. If you use the HTML code
from this projects Source Code then the page will look something like Figure 12.
Tip: The HTML page sent by this sketch includes a reference to an image
of an LDR from a different website the image itself is not sent to the
browser by the Arduino. Project 5 Building a More Advanced Web Server
shows how files such as images can be loaded from the SD card and sent
to web clients.
mark.
3. Check if the string is equal to / or /index.html. Return an HTTP error 404 file
not found if it is not. Call sendWebpage() if it is.
Replace the sketchs loop() function with this code:
void loop() {
EthernetClient client = myServer.available();
if (client) {
Serial.println("Incoming connection");
while (client.connected()) {
if (client.available() >= 4)
break;
}
char cMethod[5] = {'G', 'E', 'T', ' ', 0};
char buf1[5] = {0, 0, 0, 0, 0};
client.read((uint8_t*)buf1, 4);
if (strcmp(buf1, cMethod) == 0) {
String cFile = "";
while (client.connected()) {
if (client.available() > 0) {
char tmp = client.read();
if ((tmpc != ' ') && (tmpc != '?'))
cFile.concat(tmp);
else
break;
if (cFile.length() > 200) {
client.println("HTTP/1.0 414 Request Too Long");
client.println("Connection:close");
client.println();
cFile = "";
break;
}
}
}
if (cFile != "") {
if (
(cFile.compareTo("/") == 0) ||
(cFile.compareTo("/index.html") == 0)
) {
sendWebpage(client);
}
else {
client.println("HTTP/1.0 404 File Not Found");
client.println("Connection:close");
client.println();
}
}
}
else {
client.println("HTTP/1.0 501 Not Implemented");
client.println("Connection:close");
client.println();
}
client.stop();
}
}
The loop waits until at least four bytes of the HTTP request have been received.
The code then checks whether the HTTP request uses the GET method: if it is a
GET request then the first four characters are GET followed by a space.
The buffer array (buf1) and the character array containing the sequence of
characters to compare to the buffer (cMethod), both have an extra zero at the
Source Code
This is the complete source code for the basic web server sketch.
#include <SPI.h>
#include <Ethernet.h>
const byte mac[] = { 0x00, 0xC3, 0xA2, 0xE6, 0x3D, 0x57 };
const byte ip[] = { 192, 168, 0, 99 };
EthernetServer myServer(80);
void setup() {
//D53 on an Arduino Mega must be an output.
pinMode(53, OUTPUT);
Serial.begin(9600);
while (!Serial);
Serial.println("Establishing network connection");
Ethernet.begin((uint8_t*)mac, (uint8_t*)ip);
Serial.print("IP Address: ");
Serial.println(Ethernet.localIP());
Serial.print("Default Gateway: ");
Serial.println(Ethernet.gatewayIP());
Serial.print("Subnet Mask: ");
Serial.println(Ethernet.subnetMask());
Serial.print("DNS Server: ");
Serial.println(Ethernet.dnsServerIP());
myServer.begin();
}
void loop() {
EthernetClient client = myServer.available();
if (client) {
Serial.println("Incoming connection");
while (client.connected()) {
if (client.available() >= 4)
break;
}
char cMethod[5] = {'G', 'E', 'T', ' ', 0};
char buf1[5] = {0, 0, 0, 0, 0};
client.read((uint8_t*)buf1, 4);
if (strcmp(buf1, cMethod) == 0) {
String cFile = "";
while (client.connected()) {
if (client.available() > 0) {
char tmp = client.read();
if ((tmp != ' ') && (tmp != '?'))
cFile.concat(tmp);
else
break;
if (cFile.length() > 200) {
client.println("HTTP/1.0 414 Request Too Long");
client.println("Connection:close");
client.println();
cFile = "";
break;
}
}
}
if (cFile != "") {
if (
(cFile.compareTo("/") == 0) ||
(cFile.compareTo("/index.html") == 0)
) {
sendWebpage(client);
}
else {
client.println("HTTP/1.0 404 File Not Found");
client.println("Connection:close");
client.println();
}
}
}
else {
client.println("HTTP/1.0 501 Not Implemented");
client.println("Connection:close");
client.println();
}
client.stop();
}
}
int getAnalogReading() {
return analogRead(0);
}
void sendWebpage(EthernetClient client) {
client.println("HTTP/1.0 200 OK");
client.println("Content-Type: text/html");
client.println("Connection:close");
client.println();
client.print("<html>");
client.print("<head>");
client.print("<title>Project 4 - Setting Up a Basic Web Server</title>");
client.print("</head>");
client.print("<body>");
client.print("<h1>Project 4 - Setting Up a Basic Web Server</h1>");
client.print("<p>The value below shows the current light reading from ");
client.print("an LDR connected to the Arduino's analog input 0.</p>");
client.print("<table cellpadding=0 cellspacing=0 border=0>");
client.print("<tr>");
client.print("<td valign='middle'><img src='http://www.gcse.com/nldr.gif' /></td>");
client.print("<td> </td>");
client.print("<td valign='middle'><b style='padding-top: 15px; display: block'>");
client.print(getAnalogReading());
client.print("</b></td>");
client.print("</tr>");
client.print("</table>");
client.print("<p><a href='/'>Refresh</a></p>");
client.print("</body>");
client.print("</html>");
}
Ethernet.begin((uint8_t*)mac, (uint8_t*)ip);
myServer.begin();
}
void sendError(int code, const __FlashStringHelper *message) {
client.print(F("HTTP/1.0 "));
client.print(code);
client.print(" ");
client.println(message);
client.println(F("Connection: close"));
client.println();
client.println(message);
}
void readFileRequest() {
byte c = 0;
char tmpc;
while (client.connected()) {
if (client.available() > 0) {
tmpc = client.read();
if ((tmp != ' ') && (tmp != '?'))
fname[c++] = tmpc;
else {
fname[c] = 0;
break;
}
if (c > 100) {
sendError(414, F("Request Too Long"));
fname[0] = 0;
break;
}
}
}
}
boolean sendDirectoryList(File *di) {
return false;
}
boolean sendFile(File *fi) {
return false;
}
void loop() {
client = myServer.available();
if (client) {
while (client.connected()) {
if (client.available() >= 4) {
client.read();
client.read();
client.read();
client.read();
break;
}
}
if (client.connected()) {
readFileRequest();
// Process the file request here.
}
client.stop();
}
}
When an incoming HTTP request is received in the sketchs loop() function, the
while loop waits until the client sends the first four characters of the request. This
should be the word GET followed by a space. Due to difficulties fitting this
project on an Arduino, assume that the client is using the GET request method. If
it uses another method or sends an invalid HTTP request, then the problem is
detected either when the sketch tries to read to the file name, or when it tries to
find a file matching that name on the SD card. The Arduino might send back the
wrong error message, but it will send back an error message.
The sketch also includes a function for sending HTTP error messages,
sendError(), and declares two incomplete functions: sendDirectoryList() and
sendFile(). You will complete sendDirectoryList() and sendFile() in this project but,
for now, have a look at the function sendError() in the code above. Its declaration
is
void sendError(int code, const __FlashStringHelper *message)
Instead, you must pass the string into the F() macro, which stores the string in
Flash memory:
sendError(500, F("Internal Server Error"))
Browsing Directories
Web servers normally disable directory browsing. However, in this project you will
complete the functions sendDirectoryList() and sendFile() to send webpages that
list the files and folders on the SD card, and allows visitors to click on files to
download them.
To implement this, there are four checks to run in the sketchs loop() method:
1. If the requested file is /, open the root directory of the SD card and call
sendDirectoryList().
2. If the requested file is not / but it is a valid directory on the SD card, open the
directory and call sendDirectoryList().
3. If the file exists but it is not a directory, open the file and call sendFile().
4. If the requested file does not exist, call sendError(404, F(File Not Found)) to
send back an error message.
Insert the following code into the sketch, replacing the comment // Process the
file request here:
if ( (fname[0] == '/') && (fname[1] == 0) ) {
File di = SD.open(fname);
if (!sendDirectoryList(&di))
sendError(500, F("Internal Server Error"));
di.close();
}
else {
if (SD.exists(fname)) {
File tmp = SD.open(fname);
if (tmp.isDirectory())
sendDirectoryList(&tmp);
else
sendFile(&tmp);
tmp.close();
}
else
sendError(404, F("File Not Found"));
}
In the function sendDirectoryList(), add code to output a simple HTML page that
displays a list of the contents of a directory. This function comprises the following
steps:
1. Check that the directory was opened successfully.
2. Rewind the directory using the method rewindDirectory(). This ensures that
the buffers used by the SD library are cleared. If you do not do this,
sometimes the directory cannot be fully listed.
3. Send a response header that tells the client that the request is successful,
using the Content-Type field to tell the client to expect an HTML page.
4. Send the first part of the HTML page to the client.
5. For each item in the directory, send HTML that shows the items name and a
link to it.
6. Send the last part of the HTML page to the client.
For example:
boolean sendDirectoryList(File *di) {
if (*di) {
di->rewindDirectory();
client.println(F("HTTP/1.0 200 OK"));
client.println(F("Content-Type: text/html"));
client.println(F("Connection: close"));
client.println();
client.println(F("<html>"));
client.print(F("<head><title>"));
client.print(fname);
client.println(F("</title></head>"));
client.println(F("<body>"));
client.print(F("<h1>Index of "));
client.print(fname);
client.println(F("</h1>"));
client.println(F("<table cellpadding=2 cellspacing=2 border=0>"));
File lsf;
while ((lsf = di->openNextFile())) {
client.println(F("<tr>"));
client.print(F("<td>"));
if (lsf.isDirectory())
client.print(F("[dir]"));
else
client.print(lsf.size());
client.print(F("</td>"));
client.print(F("<td>"));
client.print(F("<a href='"));
client.print(fname);
if (fname[1] != 0)
client.print(F("/"));
client.print(lsf.name());
client.print(F("'>"));
client.print(lsf.name());
client.print(F("</a>"));
client.print(F("</td>"));
client.println(F("</tr>"));
lsf.close();
}
client.println(F("</table>"));
client.println(F("</body>"));
client.println(F("</html>"));
return true;
}
else
return false;
}
Note the use of -> because the argument passed into sendDirectoryList() is a
pointer to an instance of the File class, it is not an actual object.
To read the contents of the directory, this routine uses the same method as shown
in Reading from SD Cards. The file path and file name of the current directory are
added to each link in the HTML output, so that the links contain the full file path for
each item.
To complete the function sendFile(), the process is much shorter:
1. Check that the file was opened successfully.
2. Send a response header that tells the client that the request is successful,
using the Content-Type field to tell the client to expect a file.2
3. Read each byte, one at a time, from the SD card and send it to the client.
The following code is an example of how to do this:
boolean sendFile(File *fi) {
if (*fi) {
client.println(F("HTTP/1.0 200 OK"));
client.println(F("Content-Type: application/octet-stream"));
client.println(F("Connection: close"));
client.println();
while (fi->available()) {
client.write(fi->read());
}
return true;
}
else
return false;
}
Tip: Remember that the Arduino SD library only supports file names in the
format 8:3. If a client directly requests a file name that does not conform
to the 8:3 format then it will not be found on the SD card. Using 8:3 is also
advantageous as you do not usually have to worry about encoding and
decoding special characters using the URL encoding scheme.
what to do with the file this serves the same purpose as the file extension on
Microsoft Windows.
The values of the Content-Type field are often called MIME types because they
are derived from the multipurpose Internet mail extensions (MIME) standard,
which is used to allow email messages to contain attachments and different types
of text. In the HTTP protocol specification they are called media types.
Some of the most common media types used on the web are:
File
Extension
Media
Type
Description
.HTM
text/html
.GIF
image/gif
.PNG
image/png
.JPG
image/jpeg
.CSS
text/css
.TXT
text/plain
The sendFile() function in the sketch currently sends all files with the media type
application/octet-stream. This will often work fine, as the web browser will interpret
the file and decide how to proceed. However, if possible, you should send the
correct Content-Type field for the file that you are sending.
To ensure that HTML documents are sent as text/htmland C source files are sent
as text/plain: change the line in sendFile() that reads client.println(Content-Type:
application/octet-stream); and replace it with code that checks the file extension
in the array fname.
client.print(F("Content-Type: "));
byte sl = strlen(fname);
if (sl > 4) {
if ((fname[sl-4]=='.') && (fname[sl-3]=='H') && (fname[sl-2]=='T') && (fname[sl-1]=='M'))
client.println(F("text/html"));
if ((fname[sl-4]=='.') && (fname[sl-3]=='C') && (fname[sl-2]=='S') && (fname[sl-1]=='S'))
client.println(F("text/css"));
else
client.println(F("application/octet-stream"));
} else if (sl > 2) {
if ((fname[sl-2]=='.') && ( (fname[sl-1]=='H') || (fname[sl-1]=='C')) )
client.println(F("text/plain"));
else
client.println(F("application/octet-stream"));
}
else
client.println(F("application/octet-stream"));
You should do this for each of the file types that your project is going to host.
However, if you run out of space in the sketch, it may be acceptable to send
important files with the correct Content-Type and others with application/octetstream.
At this point, the web server sketch serves HTML and CSS files in a way that web
browsers can understand. If you decide to use a CSS file in a webpage, you
should save the style sheet in the 8:3 file name format. For example:
NORMAL.CSS. You can add references to this style sheet in your HTML files by
using a link element in the head element of each webpage:
<link href="/NORMAL.CSS" rel="stylesheet" type="text/css">
The code tags used in this project are formed by two percent signs followed by a
single character. For example, %%r.
Save the HTML code below to a file named TEST.ASP in the root directory of your
SD card.
<html>
<head>
<title>Project 5 - Building a More Advanced Web Server</title>
</head>
<body>
<h1>Project 5 - Building a More Advanced Web Server</h1>
<p>Demonstrates pre-processing a file and creating template webpages.</p>
<p>Random number: %%r.</p>
<p>Not a valid code tag: %%e.</p>
<p>Just a percent sign: %</p>
<p><a href='/TEST.ASP'>Refresh</a></p>
</body>
</html>
In order for the sketch to replace these tags with values, add the following code to
the sendFile() function in your sketch. Place this routine after the call to
client.println() that sends the blank line marking the end of the HTTP response
header fields.
if (sl > 4) {
if ((fname[sl-4]=='.') && (fname[sl-3]=='A') && (fname[sl-2]=='S') && (fname[sl-1]=='P')) {
char tmpc;
boolean found_mark1 = false;
boolean found_mark2 = false;
while (fi->available()) {
tmpc = fi->read();
if ((tmpc == '%') && (!found_mark1))
found_mark1 = true;
else if ((tmpc == '%') && (found_mark1))
found_mark2 = true;
else {
if ((found_mark1) && (found_mark2)) {
switch (tmpc) {
case 'r':
client.print(random());
break;
}
}
else if ((found_mark1) && (!found_mark2)) {
client.print('%');
client.write(tmpc);
}
else
client.write(tmpc);
found_mark1 = found_mark2 = false;
}
}
return true;
}
}
If the requested file has the extension .ASP, then this routine reads characters
from the file until it finds two percent signs next to each other. When that happens,
the next character that is read from the file is processed in a switch statement to
determine what the sketch writes into the HTML page instead of the code tag.
Once the routine completes, it returns true so that the remainder of the sendFile()
function does not run.
If the sketch finds one percent sign, but does not find a second immediately after
it, it sends a percent sign in addition to the character that has just been read from
the file. This is so that individual percent signs, not being used as code tags, are
sent correctly.
When accessed from a web browser, the file TEST.ASP looks like Figure 13.
In cases where the character following the marker %% is an r, this code sends a
random number using the Arduinos function random(). You can certainly extend
this logic to insert variables, or values obtained by reading analog sensors.
For example, to extend the sketch to support a code tag %%p, which inserts the
level of light measured by a photocell:
1. Connect a light-dependent resistor (LDR, or photocell) using the instructions
in Reading from an Analog Sensor.
2. Copy the function getAnalogReading() from Project 4 Setting up a Basic
Web Server and paste it into this sketch.
3. Add the following statements after the break statement in the function
sendFile():
case 'p':
client.print(getAnalogReading());
break;
connection per port. While you can accept one connection on port 80 and another
connection on port 81, you cannot keep two connections on port 80 independent.
In certain applications, it is often acceptable to allocate different ports for different
clients. To do so:
1. Create an instance of the EthernetServer class for each port (up to four).
2. In the sketchs loop() function, check each EthernetServer instance in turn.
3. Get a different instance of the EthernetClient class from each instance of
EthernetServer.
4. Process each request one by one.
If you do try to declare multiple instances of EthernetServer using the same port,
you will find that writing to one instance of the EthernetClient class causes the
data to be sent to all of the instances sharing that port.
There is another limitation to be aware of before doing this: the lack of
multitasking or threading on the Arduino platform. Without these features, a loop
like the one below will still prevent any other clients from connecting when the
server is sending a large file.
while(fi->available()) {
client.write(fi->read());
}
To solve this problem, you need to change how HTTP requests are processed
only reading and sending a few bytes from each file before dropping back to the
sketchs loop() function in order to do the same on another connection. This
means storing the state of up to four requests, and it is not a trivial task to do all of
this without exhausting the Arduinos limited resources.
Source Code
The complete source code for this project is shown below.
#include <SPI.h>
#include <Ethernet.h>
#include <SD.h>
const byte mac[] = { 0x00, 0xC0, 0xA2, 0xE6, 0x3D, 0x54 };
const byte ip[] = { 192, 168, 0, 99 };
EthernetServer myServer(80);
EthernetClient client;
char fname[100];
void setup() {
//D53 on the Arduino Mega must be an output.
pinMode(53, OUTPUT);
SD.begin(4);
Ethernet.begin((uint8_t*)mac, (uint8_t*)ip);
myServer.begin();
}
void sendError(int code, const __FlashStringHelper *message) {
client.print(F("HTTP/1.0 "));
client.print(code);
client.print(" ");
client.println(message);
client.println(F("Connection: close"));
client.println();
client.println(message);
}
void readFileRequest() {
byte c = 0;
char tmpc;
while (client.connected()) {
if (client.available() > 0) {
tmpc = client.read();
if ((tmpc != ' ') && (tmpc != '?'))
fname[c++] = tmpc;
else {
fname[c] = 0;
break;
}
if (c > 100) {
sendError(414, F("Request Too Long"));
fname[0] = 0;
break;
}
}
}
}
boolean sendDirectoryList(File *di) {
if (*di) {
di->rewindDirectory();
client.println(F("HTTP/1.0 200 OK"));
client.println(F("Content-Type: text/html"));
client.println(F("Connection: close"));
client.println();
client.println(F("<html>"));
client.print(F("<head><title>"));
client.print(fname);
client.println(F("</title></head>"));
client.println(F("<body>"));
client.print(F("<h1>Index of "));
client.print(fname);
client.println(F("</h1>"));
client.println(F("<table cellpadding=2 cellspacing=2 border=0>"));
File lsf;
while ((lsf = di->openNextFile())) {
client.println(F("<tr>"));
client.print(F("<td>"));
if (lsf.isDirectory())
client.print(F("[dir]"));
else
client.print(lsf.size());
client.print(F("</td>"));
client.print(F("<td>"));
client.print(F("<a href='"));
client.print(fname);
if (fname[1] != 0)
client.print(F("/"));
client.print(lsf.name());
client.print(F("'>"));
client.print(lsf.name());
client.print(F("</a>"));
client.print(F("</td>"));
client.println(F("</tr>"));
lsf.close();
}
client.println(F("</table>"));
client.println(F("</body>"));
client.println(F("</html>"));
return true;
}
else
return false;
}
boolean sendFile(File *fi) {
if (*fi) {
client.println(F("HTTP/1.0 200 OK"));
client.print(F("Content-Type: "));
byte sl = strlen(fname);
if (sl > 4) {
if ((fname[sl-4]=='.') && (fname[sl-3]=='H') && (fname[sl-2]=='T') && (fname[sl-1]=='M'))
client.println(F("text/html"));
else if ((fname[sl-4]=='.') && (fname[sl-3]=='C') && (fname[sl-2]=='S') && (fname[sl-1]=='S'))
client.println(F("text/css"));
else if ((fname[sl-4]=='.') && (fname[sl-3]=='A') && (fname[sl-2]=='S') && (fname[sl-1]=='P'))
client.println(F("text/html"));
else
client.println(F("application/octet-stream"));
} else if (sl > 2) {
if ((fname[sl-2]=='.') && ( (fname[sl-1]=='H') || (fname[sl-1]=='C')) )
client.println(F("text/plain"));
else
client.println(F("application/octet-stream"));
}
else
client.println(F("application/octet-stream"));
client.println(F("Connection: close"));
client.println();
if (sl > 4) {
if ((fname[sl-4]=='.') && (fname[sl-3]=='A') && (fname[sl-2]=='S') && (fname[sl-1]=='P')) {
char tmpc;
boolean found_mark1 = false;
boolean found_mark2 = false;
while (fi->available()) {
tmpc = fi->read();
if ((tmpc == '%') && (!found_mark1))
found_mark1 = true;
else if ((tmpc == '%') && (found_mark1))
found_mark2 = true;
else {
if ((found_mark1) && (found_mark2)) {
switch (tmpc) {
case 'r':
client.print(random());
break;
}
}
else if ((found_mark1) && (!found_mark2)) {
client.print('%');
client.write(tmpc);
}
else
client.write(tmpc);
found_mark1 = found_mark2 = false;
}
}
return true;
}
}
while (fi->available()) {
client.write(fi->read());
}
return true;
}
else
return false;
}
void loop() {
client = myServer.available();
if (client) {
while (client.connected()) {
if (client.available() >= 4) {
client.read();
client.read();
client.read();
client.read();
break;
}
}
if (client.connected()) {
readFileRequest();
if (
(fname[0] == '/') &&
(fname[1] == 0)
)
{
File di = SD.open(fname);
if (!sendDirectoryList(&di))
sendError(500, F("Internal Server Error"));
di.close();
}
else {
if (SD.exists(fname)) {
File tmp = SD.open(fname);
if (tmp.isDirectory())
sendDirectoryList(&tmp);
else
sendFile(&tmp);
tmp.close();
}
else
sendError(404, F("File Not Found"));
}
}
client.stop();
}
}
A web browser sending this request is asking for the file myfile.txt, and it is
sending two parameters that the server can use help it process the request. Each
parameter is made up of a key and a value which are separated by an equals
sign. Parameters are separated by ampersands, and the data string is separated
from the file name by a question mark.
This string ?key1=value1&key2=value2 is called a query string.
While query strings and POST data are very simple to work with on most systems,
they are often inconvenient in Arduino projects due to the amount of memory it
takes to store and process them.
"values": [1, 2, 3, 4, 5, 6, 7, 8, 9]
}
When evaluated by the JSON parser, this data creates an object with two
properties: result (which contains the text string ok), and values (an array of
numbers, 19).
jQuery includes the method getJSON() which you can use to make a web request
to the Arduino without reloading the current webpage. getJSON() expects a
response from the web server in JSON format, and will automatically parse this
response into a JavaScript object.
You will also need the jQuery Rotate plugin from code.google.com/p/jqueryrotate/.
Download the file jQueryRotate.js from the website and save it to the root of the
SD card as ROTATE.JS.
To ensure that JavaScript (.JS) files are sent with the correct Content-Type, you
will need to modify the Arduino web server that you created in Project 5. In the
function sendFile(), before the line
} else if (sl > 2) {
The example HTML webpage used in this book includes images in the portable
network graphics (PNG) format. To ensure that these images are sent with the
correct Content-Type, after the lines:
else if ((fname[sl-4]=='.') && (fname[sl-3]=='A') && (fname[sl-2]=='S') && (fname[sl-1]=='P'))
client.println(F("text/html"));
You control servos using pulses, and the easiest way to do this is to connect the
servos to the Arduino (through the Ethernet Shield) on pins that are marked
PWM. Since the Ethernet Shield uses digital pin 10, connect the servos on pins
9, 6, 5, and 3.
Standard servos allow the rotation of the shaft to be set between 0 and 180
degrees, with 90 as the center point. This sketch should work with all standard RC
(hobby) servo motors that connect via three pins 5V, pulse, ground. However,
you should check the datasheet for the parts that you have, to ensure that you are
operating the component within its capabilities and have wired it up correctly.
The Arduino IDE comes with a library for controlling servos Servo.h. Include this
library at the top of the sketch:
#include <Servo.h>
Add the following definitions near to the top of the Arduino sketch:
Servo servo1, servo2, servo3, servo4;
byte Angle1 = 90;
byte Angle2 = 90;
byte Angle3 = 90;
byte Angle4 = 90;
This creates four instances of the Servo class and these control the servo motors
connected to the Arduino. The other four lines declare four bytes that hold the
current angle of rotation of each servo. At the start of the sketch, all four servos
are at zero degrees rotation.
To prepare each servo for use, call the method attach() from each instance of the
Servo class. Then use the method write() to move the servo into its center
position it may have moved while connecting the wires, or could be out of
position if the sketch has been run previously. Add the following lines to the
setup() function in your sketch:
servo1.attach(9);
servo2.attach(6);
servo3.attach(5);
servo4.attach(3);
servo1.write(90);
servo2.write(90);
servo3.write(90);
servo4.write(90);
<script src="/ROTATE.JS"></script>
<style type="text/css">
body { background-color: #D0D2D3; }
h1 { text-align: center; }
input { width: 50px; border: 1px solid black; margin-right: 30px; }
p { text-align: center; }
#panel { display: block;
width: 340px; height: 100px;
margin: 30px auto 10px auto; padding: 0 0 0 30px;
}
.knob { display: inline-block; width: 50px; background: url(BACK.PNG) top left no-repeat;
margin-right: 30px;
}
</style>
</head>
<body>
<h1>Project 6 Controlling Digital Outputs from the Web</h1>
<div id="panel">
<div class="knob"><img src="KNOB.PNG" alt="" id="knob1" /></div>
<div class="knob"><img src="KNOB.PNG" alt="" id="knob2" /></div>
<div class="knob"><img src="KNOB.PNG" alt="" id="knob3" /></div>
<div class="knob"><img src="KNOB.PNG" alt="" id="knob4" /></div>
<br/>
<input type="text" value="0" id="angle1" />
<input type="text" value="0" id="angle2" />
<input type="text" value="0" id="angle3" />
<input type="text" value="0" id="angle4" />
</div>
<p>Enter a number between 0 and 180 in a box and press Return/Enter.</p>
</body>
</html>
The knobs in Figure 16 are created using two portable network graphics (PNG)
files one for the knob itself, and another that is displayed underneath and
provides a little background shadow.
Save this webpage to the root of the SD card that you are using with the Arduino,
with a file name in the 8:3 format, such as SERVOS.HTM.
The next step is to add some JavaScript control to the textboxes using jQuery. In
the webpages head element, add the following script block:
<script language="javascript">
// defines a function that allows only numbers and some basic cursor
The JavaScript function checkBox() accepts a jQuery event object (which contains
details about the event that occurs when the user presses a key when one of the
textboxes has the focus) and uses this information to block any non-numeric
characters from the textbox with the exception of certain special key presses
such as the Home and End keys.
When the user presses the Enter key (13), checkBox() converts the string that is
in the textbox to an integer number, and passes this number into the function,
sendRotation(). Currently, sendRotation() does nothing, but will be used to send
the command to the Arduino.
Before checkBox() can respond to user key presses, you must add it as a handler
for the keydown event on all four of the textboxes. This is done from jQuerys
ready() function so that the web browser only attempts to add the handlers once
the full document has been received from the web server, ensuring that the
textboxes are first included in the document object model (DOM).
Webpage
Description
/gs
/ss
When the JavaScript code makes a request for the file /gs, the sketch will
currently return a file not found error message, because the file is not on the SD
card. You want to change this behavior.
In the sketchs loop() function, find the lines that check whether the file exists on
the SD card. The final three lines of this code send the error message:
}
else
sendError(404, F(File Not Found));
Before the curly brace and else statement, add the following code to process
requests for /gs:
else if ( (fname[0]=='/') && (fname[1]=='g') && (fname[2] == 's') && (fname[3]==0) ) {
client.println(F("HTTP/1.0 200 OK"));
client.println(F("Content-Type: application/json"));
client.println(F("Cache-Control: no-cache, no-store, must-revalidate"));
client.println(F("Pragma: no-cache"));
client.println(F("Expires: 0"));
client.println(F("Connection: close"));
client.println();
client.print(F("{\"result\":\"ok\", \"values\":["));
client.print(Angle1);
client.print(F(","));
client.print(Angle2);
client.print(F(","));
client.print(Angle3);
client.print(F(","));
client.print(Angle4);
client.print(F("]}"));
}
This code causes the sketch to send the values of the four Angle variables as
JSON data, {result:ok, values:[0,0,0,0]}, when the web browser requests /gs.
Processing requests for /ss is a little more involved. When complete, your
JavaScript code will append the number of the servo and the angle of rotation to
the file name. This is a little simpler for the Arduino sketch to process than a full
query string.
The first line of the HTTP request will look a little like this:
GET /ss1270 HTTP/1.1
In the sketchs loop() function, add a clause to the if statement that checks the
requested file. This clause checks:
1. That the file name is at least three characters long.
2. That the first three characters of the requested file name are /ss.
3. That the file name contains characters after the /ss, and whether or not to
Before the curly brace and else statement, add the following code:
else if ((strlen(fname) >= 3) && (fname[0]=='/') && (fname[1]=='s') && (fname[2]=='s') ) {
if (strlen(fname)==3) {
client.println(F("HTTP/1.0 200 OK"));
client.println(F("Content-Type: application/json"));
client.println(F("Cache-Control: no-cache, no-store, must-revalidate"));
client.println(F("Pragma: no-cache"));
client.println(F("Expires: 0"));
client.println(F("Connection: close"));
client.println();
client.println(F("{\"result\":\"error\", \"message\":\"Invalid command\"}"));
}
else {
char buffer[4];
byte i;
for (i=4; i < strlen(fname); i++)
buffer[i-4] = fname[i];
buffer[i-4] = 0;
byte angle = atoi(buffer);
switch (fname[3]) {
case '0':
Angle1 = angle;
servo1.write(180 - angle);
break;
case '1':
Angle2 = angle;
servo2.write(180 - angle);
break;
case '2':
Angle3 = angle;
servo3.write(180 - angle);
break;
case '3':
Angle4 = angle;
servo4.write(180 - angle);
break;
}
client.print(F("{\"result\":\"ok\", \"values\":["));
client.print(Angle1);
client.print(F(","));
client.print(Angle2);
client.print(F(","));
client.print(Angle3);
client.print(F(","));
client.print(Angle4);
client.print(F("]}"));
}
If the file name is only three characters long then the web client may have made a
request for /ss but did not include the servo number and angle of rotation. In these
circumstances, the sketch returns a successful HTTP response but the JSON
data tells the JavaScript code that there is an error.
If the web client did send all of the necessary information, the code then attempts
to convert the characters that represent the angle of rotation into a number. Note
that this code does very little error checking.
The switch statement is used to set the Angle variables appropriately, depending
on which servo is selected, and move the servo using the method write() from the
selected instance of the Servo class.
Tip: If you have a servo that turns clockwise, not counterclockwise,
change the calls to write() so that the angle is not subtracted from 180.
Finally, the /ss command completes its operation by returning the status of the all
of the servers, in the same way as a /gs command.
These two pieces of information are appended to the file name that the call to
getJSON() requests from the Arduino. When that request completes, the function
that is written into the call to getJSON() is executed. In this case, if the server
returned a JSON object with the property result set to ok then the data is passed
into updateDisplay(), which updates the contents of the textboxes and rotates the
images.
Add updateDisplay() to your script block:
function updateDisplay(data) {
$("#angle1").val(data.values[0]);
$("#angle2").val(data.values[1]);
$("#angle3").val(data.values[2]);
$("#angle4").val(data.values[3]);
$("#knob1").rotate(data.values[0]);
$("#knob2").rotate(data.values[1]);
$("#knob3").rotate(data.values[2]);
$("#knob4").rotate(data.values[3]);
}
Now when the user types a number into a textbox and presses Enter,
sendRotation() sends the number they type to the Arduino and the UI is updated
to show the most recent positions of all four servos.
However, when the webpage is refreshed, the values in the textboxes return to
zero. You can add some code into the jQuery ready() function so that when the
page is loaded, it fetches the most recent positions of the servos from the
Arduino. Do this by making a getJSON() call to /gs from the ready() function.
For example:
$.ajaxSetup({cache:false});
$.getJSON("/gs", function(data) {
if (data.result == "ok") {
updateDisplay(data);
}
});
Value
Description
CacheControl
no-cache, no-store,
must-revalidate
Pragma
no-cache
Expires
jQuerys getJSON() function caches more than most web browsers. In addition to
sending the HTTP header fields, you should turn off this caching in jQuery. Include
this statement in the jQuery ready() function:
$.ajaxSetup({cache:false});
With caching disabled, jQuery actually adds unique numbers to the query string of
each web request. In the source code for this project, you can see that the
function readFileRequest() already detects a question mark as the end of the file
name, and it ignores query strings.
Without this mechanism, even a simple request for /gs would include a query
string that your sketch needs to account for.
) {
return;
}
if (e.keyCode == 13) {
e.preventDefault();
var angle = parseInt(e.currentTarget.value) || 0;
if (angle > 180)
angle = 180;
e.currentTarget.value = angle;
e.currentTarget.blur();
sendRotation(e.currentTarget.id, angle);
}
else if (
(e.shiftKey || (e.keyCode < 48 || e.keyCode > 57)) &&
(e.keyCode < 96 || e.keyCode > 105)
) {
e.preventDefault();
}
}
function sendRotation(box, angle) {
var servo = 0;
switch (box) {
case "angle1":
servo = 0;
break;
case "angle2":
servo = 1;
break;
case "angle3":
servo = 2;
break;
case "angle4":
servo = 3;
break;
}
$.getJSON("/ss" + servo + angle, function(data) {
if (data.result == "ok") {
updateDisplay(data);
} else {
alert(data.message);
}
});
}
function updateDisplay(data) {
$("#angle1").val(data.values[0]);
$("#angle2").val(data.values[1]);
$("#angle3").val(data.values[2]);
$("#angle4").val(data.values[3]);
$("#knob1").rotate(data.values[0]);
$("#knob2").rotate(data.values[1]);
$("#knob3").rotate(data.values[2]);
$("#knob4").rotate(data.values[3]);
}
// add the handlers to the four textboxes and load the
// position of the servos.
$(document).ready(function() {
$("#angle1").keydown(checkBox);
$("#angle2").keydown(checkBox);
$("#angle3").keydown(checkBox);
$("#angle4").keydown(checkBox);
$.ajaxSetup({cache:false});
$.getJSON("/gs", function(data) {
if (data.result == "ok") {
updateDisplay(data);
}
});
});
</script>
</head>
<body>
<h1>Project 6 Controlling Digital Outputs from the Web</h1>
<div id="panel">
<div class="knob"><img src="KNOB.PNG" alt="" id="knob1" /></div>
<div class="knob"><img src="KNOB.PNG" alt="" id="knob2" /></div>
<div class="knob"><img src="KNOB.PNG" alt="" id="knob3" /></div>
<div class="knob"><img src="KNOB.PNG" alt="" id="knob4" /></div>
<br/>
<input type="text" value="0" id="angle1" />
<input type="text" value="0" id="angle2" />
<input type="text" value="0" id="angle3" />
<input type="text" value="0" id="angle4" />
</div>
<p>Enter a number between 0 and 180 in a box and press Return/Enter.</p>
</body>
</html>
if (client.available() > 0) {
tmpc = client.read();
if ((tmpc != ' ') && (tmpc != '?'))
fname[c++] = tmpc;
else {
fname[c] = 0;
break;
}
if (c > 100) {
sendError(414, F("Request Too Long"));
fname[0] = 0;
break;
}
}
}
}
boolean sendDirectoryList(File *di) {
if (*di) {
di->rewindDirectory();
client.println(F("HTTP/1.0 200 OK"));
client.println(F("Content-Type: text/html"));
client.println(F("Connection: close"));
client.println();
client.println(F("<html>"));
client.print(F("<head><title>"));
client.print(fname);
client.println(F("</title></head>"));
client.println(F("<body>"));
client.print(F("<h1>Index of "));
client.print(fname);
client.println(F("</h1>"));
client.println(F("<table cellpadding=2 cellspacing=2 border=0>"));
File lsf;
while ((lsf = di->openNextFile())) {
client.println(F("<tr>"));
client.print(F("<td>"));
if (lsf.isDirectory())
client.print(F("[dir]"));
else
client.print(lsf.size());
client.print(F("</td>"));
client.print(F("<td>"));
client.print(F("<a href='"));
client.print(fname);
if (fname[1] != 0)
client.print(F("/"));
client.print(lsf.name());
client.print(F("'>"));
client.print(lsf.name());
client.print(F("</a>"));
client.print(F("</td>"));
client.println(F("</tr>"));
client.flush();
lsf.close();
}
client.println(F("</table>"));
client.println(F("</body>"));
client.println(F("</html>"));
return true;
}
else
return false;
}
boolean sendFile(File *fi) {
if (*fi) {
client.println(F("HTTP/1.0 200 OK"));
client.print(F("Content-Type: "));
byte sl = strlen(fname);
if (sl > 4) {
if ((fname[sl-4]=='.') && (fname[sl-3]=='H') && (fname[sl-2]=='T') && (fname[sl-1]=='M'))
client.println(F("text/html"));
else if ((fname[sl-4]=='.') && (fname[sl-3]=='C') && (fname[sl-2]=='S') && (fname[sl-1]=='S'))
client.println(F("text/css"));
else if ((fname[sl-4]=='.') && (fname[sl-3]=='A') && (fname[sl-2]=='S') && (fname[sl-1]=='P'))
client.println(F("text/html"));
else if ((fname[sl-4]='.') && (fname[sl-3]=='P') && (fname[sl-2]=='N') && (fname[sl-1]=='G'))
client.println(F("image/png"));
else
client.println(F("application/octet-stream"));
} else if (sl > 3) {
if ((fname[sl-3]=='.') && (fname[sl-2]=='J') && (fname[sl-1]=='S'))
client.println(F("text/plain"));
else
client.println(F("application/octet-stream"));
} else if (sl > 2) {
if ((fname[sl-2] == '.') && ( (fname[sl-1] == 'H') || (fname[sl-1] == 'C')) )
client.println(F("text/plain"));
else
client.println(F("application/octet-stream"));
}
else
client.println(F("application/octet-stream"));
client.println(F("Connection: close"));
client.println();
if (sl > 4) {
if ((fname[sl-4]=='.') && (fname[sl-3]=='A') && (fname[sl-2]=='S') && (fname[sl-1]=='P')) {
char tmpc;
boolean found_mark1 = false;
boolean found_mark2 = false;
while (fi->available()) {
tmpc = fi->read();
if ((tmpc == '%') && (!found_mark1))
found_mark1 = true;
else if ((tmpc == '%') && (found_mark1))
found_mark2 = true;
else {
if ((found_mark1) && (found_mark2)) {
switch (tmpc) {
case 'r':
client.print(random());
break;
}
}
else if ((found_mark1) && (!found_mark2)) {
client.print('%');
client.write(tmpc);
}
else
client.write(tmpc);
found_mark1 = found_mark2 = false;
}
}
return true;
}
}
while (fi->available()) {
client.write(fi->read());
}
return true;
}
else
return false;
}
void loop() {
client = myServer.available();
if (client) {
while (client.connected()) {
if (client.available() >= 4) {
client.read();
client.read();
client.read();
client.read();
break;
}
}
if (client.connected()) {
readFileRequest();
if (
(fname[0] == '/') &&
(fname[1] == 0)
)
{
File di = SD.open(fname);
if (!sendDirectoryList(&di))
sendError(500, F("Internal Server Error"));
di.close();
}
else {
if (SD.exists(fname)) {
File tmp = SD.open(fname);
if (tmp.isDirectory())
sendDirectoryList(&tmp);
else
sendFile(&tmp);
tmp.close();
}
else if ( (fname[0]=='/') && (fname[1]=='g') && (fname[2] == 's') && (fname[3]==0) ) {
client.println(F("HTTP/1.0 200 OK"));
client.println(F("Content-Type: application/json"));
client.println(F("Cache-Control: no-cache, no-store, must-revalidate"));
client.println(F("Pragma: no-cache"));
client.println(F("Expires: 0"));
client.println(F("Connection: close"));
client.println();
client.print(F("{\"result\":\"ok\", \"values\":["));
client.print(Angle1);
client.print(F(","));
client.print(Angle2);
client.print(F(","));
client.print(Angle3);
client.print(F(","));
client.print(Angle4);
client.print(F("]}"));
}
else if ((strlen(fname)>=3) && (fname[0]=='/') && (fname[1]=='s') && (fname[2]=='s') ) {
if (strlen(fname)==3) {
client.println(F("HTTP/1.0 200 OK"));
client.println(F("Content-Type: application/json"));
client.println(F("Cache-Control: no-cache, no-store, must-revalidate"));
client.println(F("Pragma: no-cache"));
client.println(F("Expires: 0"));
client.println(F("Connection: close"));
client.println();
client.println(F("{\"result\":\"error\", \"message\":\"Invalid command\"}"));
}
else {
char buffer[4];
byte i;
for (i=4; i < strlen(fname); i++)
buffer[i-4] = fname[i];
buffer[i-4] = 0;
byte angle = 180 - atoi(buffer);
switch (fname[3]) {
case '0':
Angle1 = angle;
servo1.write(180 - angle);
break;
case '1':
Angle2 = angle;
servo2.write(180 - angle);
break;
case '2':
Angle3 = angle;
servo3.write(180 - angle);
break;
case '3':
Angle4 = angle;
servo4.write(180 - angle);
break;
}
client.print(F("{\"result\":\"ok\", \"values\":["));
client.print(Angle1);
client.print(F(","));
client.print(Angle2);
client.print(F(","));
client.print(Angle3);
client.print(F(","));
client.print(Angle4);
client.print(F("]}"));
}
}
else
sendError(404, F("File Not Found"));
}
}
client.stop();
}
}
The Content-Type field is discussed in more detail in Understanding MIME and Media Types.
The media type application/octet-stream informs the browser to expect a binary file that it can process
however it feels is best.
3
Remember to rename the file so that it follows the 8:3 file name format.
To start the library code and monitor a port, use the begin() method of the
EthernetUDP class. This accepts one argument the UDP port number to
communicate on.
The basic sketch below connects to the network and declares an instance of the
EthernetUDP class. You will build on this sketch as you progress through the
project, until you have a working DNS server.
#include <SPI.h>
#include <Ethernet.h>
#include <EthernetUDP.h>
const byte mac[] = { 0x00, 0xC3, 0xA2, 0xE6, 0x3D, 0x54 };
byte ip[] = { 192, 168, 0, 99 };
IPAddress dns_server(8,8,8,8);
byte pbuf[512];
EthernetUDP udp;
void setup() {
//D53 on the Arduino Mega must be an output.
pinMode(53, OUTPUT);
Serial.begin(9600);
while (!Serial);
Serial.println("Establishing network connection");
Ethernet.begin((uint8_t*)mac, (uint8_t*)ip);
Serial.print("IP Address: ");
Serial.println(Ethernet.localIP());
Serial.print("Opening UDP port ");
if (udp.begin(53) == 1)
Serial.println("OK!");
else
Serial.println("FAILED!");
}
void loop() {
}
The global variable declarations will be familiar to you if you have completed the
read() accepts two arguments. The first is a pointer to an area of memory in which
to store the bytes read from the message. The second is the maximum number of
bytes to read the value returned by the function indicates how many bytes are
actually read.
Now that the DNS message is stored in a buffer array, you can process the
request.
In the next section, you will modify the sketchs loop() function so that when the
Arduino receives a DNS request, it forwards this request to another DNS server.
When the Arduino receives a response to that request, it will forward the response
to the original client.
Type
Name
Description
01
Unsigned
integer
Transaction
ID
23
Unsigned
integer
Flags
The parameter sz specifies how long the DNS request is, since the allocated size
of the buffer pbuf is often longer than the clients request.
When the Arduino forwards the request to another DNS server, it needs to be able
to identify when it receives the response message that matches the request. To
do this, the code above changes the transaction ID of the DNS request to a
random 16-bit number.
Tip: When conforming to the DNS protocol, the most-significant byte of a
16-bit integer should be sent first. The Arduino is little-endian and so the
two lines that set the bytes in pbuf ensure that the two halves of the
random transaction ID are divided correctly.
After a call to the EthernetUDP class method begin(), you can begin writing DNS
messages to the UDP port. You do not need to open a connection to a server.
Instead, the intended recipient of the message is specified in the message itself.
This code uses three methods of the EthernetUDP class:
Method
Description
beginPacket()
write()
endPacket()
After sending the message, the echo_DNS_Lookup() function should wait for a
DNS response message that has the same transaction ID. Add the following code
before the line return 0; in echo_DNS_Lookup():
long timeout = millis() + 1000;
while (true) {
int result = udp.parsePacket();
if (result > 0) {
udp.read(pbuf, sizeof(pbuf));
if ( (pbuf[2] & 0x80) == 0x80) &&
(pbuf[0] == ((byte)(tid >> 8))) &&
(pbuf[1] == ((byte)(tid & 0x0000FFFF)))
)
return result;
if (millis() > timeout)
return 0;
}
delay(10);
}
To ensure that only DNS response messages are processed, this code checks
whether bit 15 of the message flags is set. If it is, the message is a DNS response
and the loop can end.
parsePacket() does not return until a UDP message is found, and so the timeout
that is implemented here is a little crude, but functional.
When the echo_DNS_Lookup() function exits, it either returns 0 to tell the calling
function that no response was received from the DNS server, or it returns the
length of the response. The response is in the buffer array pbuf this overwrites
the clients original DNS request, but at this point that is no longer needed.
Serial.begin(9600);
while (!Serial);
Serial.println("Establishing network connection");
Ethernet.begin((uint8_t*)mac, (uint8_t*)ip);
Serial.print("IP Address: ");
Serial.println(Ethernet.localIP());
Serial.print("Opening UDP port ");
if (udp.begin(53) == 1)
Serial.println("OK!");
else
Serial.println("FAILED!");
}
int echo_DNS_Lookup(int sz) {
int tid = random(0xFFFF);
pbuf[0] = (byte)(tid >> 8);
pbuf[1] = (byte)(tid & 0x0000FFFF);
udp.beginPacket(dns_server, 53);
udp.write(pbuf, sz);
udp.endPacket();
long timeout = millis() + 1000;
while (true) {
int result = udp.parsePacket();
if (result > 0) {
udp.read(pbuf, sizeof(pbuf));
if ( ((pbuf[2] & 0x80) == 0x80) &&
(pbuf[0] == ((byte)(tid >> 8))) &&
(pbuf[1] == ((byte)(tid & 0x0000FFFF)))
)
return result;
if (millis() > timeout)
return 0;
}
delay(10);
}
return 0;
}
void loop() {
int psize = udp.parsePacket();
if (psize > 0) {
udp.read(pbuf, sizeof(pbuf));
if ((pbuf[2] & 0x80) == 0) {
IPAddress client = udp.remoteIP();
unsigned int clientPort = udp.remotePort();
int tid = (pbuf[0] << 8) | pbuf[1];
int res = echo_DNS_Lookup(psize);
if (res > 0) {
pbuf[0] = (byte)(tid >> 8);
pbuf[1] = (byte)(tid & 0x0000FFFF);
udp.beginPacket(client, clientPort);
udp.write(pbuf, res);
udp.endPacket();
}
}
}
delay(10);
}
On Windows 8/7/Vista/XP:
1. Press the Windows logo key + R.
2. Type cmd, then press Enter.
3. Type nslookup www.connectingarduino.com 192.168.0.99 and press Enter.
Change the IP address if your Arduino connects to your network using a
different IP address.
Nslookup makes DNS requests for the specified domain, and if an additional
server name or IP is included, it will use the specified nameserver. You should see
a response similar to Figure 17.
On Mac OS X:
1. On the dock, click Finder.
2. On the sidebar, click Applications.
3. Click Utilities, then double-click Terminal.
4. Type nslookup www.connectingarduino.com -server 192.168.0.99 and press
Enter. Change the IP address if your Arduino connects to your network using
a different IP address.
If the Arduino sketch is functioning correctly, then you should see very little
difference between running nslookup without specifying the nameserver and when
you run it using the IP address of the Arduino.
In the next section, you will see the structure of DNS requests and responses.
One of the pieces of information that a DNS server adds to its response is the
time-to-live value (TTL). This indicates how long DNS clients are allowed to cache
the record before they should request a new copy from the nameserver. If you find
that nslookup is caching the information, preventing you from testing effectively
while you work on this project, you can clear the DNS cache.
On Windows 8/7/Vista/XP:
Type
Name
Description
01
Unsigned
integer
Transaction
ID
23
Unsigned
integer
Flags
See below.
45
Unsigned
integer
Questions
67
Unsigned
integer
Answer
RRs
89
Unsigned
integer
Authority
RRs
10
11
Unsigned
integer
Additional
RRs
12
Queries
See below.
The flags field packs 10 pieces of information in a 16-bit structure. Only five items
are needed for DNS requests the other bits are clear.
Bit
Name
Description
15
QR
14
11
Opcode
TC
Truncation. Specifies that the DNS message was too long for a
single UDP message and is split across multiple. This project
assumes requests and responses are not truncated (0).
RD
64
Reserved. Should be 0.
After the header structure, which is always 12 bytes, the client sends a number of
query structures, the exact number of which is set in the header field Questions.
Each query is of variable length, due to the way the domain names are included,
but they are made up of three sections: QNAME (the domain name), QTYPE (the
type of DNS record being requested 0x0001 for an A record that translates
domain names to IP addresses), and QCLASS (0x0001 for Internet queries).
The domain name is divided into pieces, corresponding to where the period is
placed when typed into a web browser. Before each piece, the client sends a byte
that specifies how many characters this part of the domain name consists of. For
example:
0x03 0x77 0x77 0x77
Length: 3
Characters: www
These pieces are sent one after the other until the number that specifies the
length of a part is zero. The following sequence represents the domain name
www.arduino.cc:
0x03 0x77 0x77 0x77 0x06 0x61 0x72 0x64 0x75 0x69 0x6e 0x6f 0x02 0x63 0x63 0x00
int i=12;
while (pbuf[i] > 0) {
if (result.compareTo("") != 0)
result.concat('.');
for (int x=1; x <= pbuf[i]; x++);
result.concat((char)pbuf[i+x]);
i = i + pbuf[i] + 1;
}
return result;
}
The first QNAME of the query will always be at position 12 (starting at the 13th
byte) in the buffer array, so this function reads the QNAME and returns a String
object that contains the domain name in a usual period-separated way.
At the top of the sketch, define a few domain names:
const int NUM_HOSTS = 3;
String hosts[] = {"dns.arduino", "my.arduino", "web.arduino"};
String ips[] = { IPAddress(192,168,0,99), IPAddress(192,168,0,10), IPAddress(192,168,0,11) };
These domain names cannot exist on the Internet since .arduino is not a valid toplevel domain.
Now, add this function to your sketch:
int isLocalDomain(String dn) {
for (int i=0; i < NUM_HOSTS; i++) {
if (dn.equalsIgnoreCase(hosts[i]))
return i;
}
return -1;
}
udp.write(pbuf, res);
udp.endPacket();
}
} else {
// Send a DNS response to the client here.
}
}
}
}
delay(10);
}
Test the sketch as described in Testing the Device. When querying local domains,
nslookup should timeout without receiving any response (see Figure 18). For all
other domains, you should receive a DNS response.
The final step in this project is to replace the comment // Send a DNS response to
the client here, with code that sends a DNS response message to the client.
In Implementing Custom Domain Names and Routing you can see the structure of
a DNS message, in particular, the structure of the header. Response messages
follow the same format as request messages except that after the queries
section, the server sends up to three sets of answers: answers, authoritative
nameservers, and additional records. Each set can contain multiple records.
However, in this project you will only send one and so the field Answer RRs (bytes
67) in the message header will be an unsigned integer with the value 1.
Answer records follow a standard format. Since the domain name is variable in
length, the byte numbers in the table below are shown assuming that +1 is the
byte following the domain name.
Byte
Type
+1
Unsigned
integer
Name
Description
NAME
TYPE
Unsigned
integer
CLASS
+5
32-bit
unsigned
integer
TTL
+9
Unsigned
integer
RDLENGTH
RDATA
+11
The code below:
udp.write((byte)ips[host_idx][2]);
udp.write((byte)ips[host_idx][3]);
udp.endPacket();
The Arduino now returns answer records that DNS clients can understand.
Finally, you might consider modifying the sketchs setup() function so that it reads
a list of domain names and IP address from the SD card. This would save you
from having to reprogram the Arduino if you add more devices and domain names
to the system.
Source Code
The full source code for this sketch is shown below. If you want to define your own
configuration2 then the declarations to change are:
Name
Description
mac
ip
dns_server
NUM_HOSTS
hosts
ips
#include <SPI.h>
#include <Ethernet.h>
#include <EthernetUDP.h>
const byte mac[] = { 0x00, 0xC3, 0xA2, 0xE6, 0x3D, 0x54 };
byte ip[] = { 192, 168, 0, 99 };
IPAddress dns_server(8,8,8,8);
byte pbuf[512];
EthernetUDP udp;
const int NUM_HOSTS = 3;
String hosts[] = {"dns.arduino", "my.arduino", "web.arduino"};
IPAddress ips[] = { IPAddress(192,168,0,99), IPAddress(192,168,0,2), IPAddress(192,168,0,3) };
void setup() {
//D53 on the Arduino Mega must be an output.
pinMode(53, OUTPUT);
Serial.begin(9600);
while (!Serial);
Serial.println("Establishing network connection");
Ethernet.begin((uint8_t*)mac, (uint8_t*)ip);
Serial.print("IP Address: ");
Serial.println(Ethernet.localIP());
udp.write((byte)ips[host_idx][0]);
udp.write((byte)ips[host_idx][1]);
udp.write((byte)ips[host_idx][2]);
udp.write((byte)ips[host_idx][3]);
udp.endPacket();
}
}
}
}
delay(10);
}
String getDomainName() {
String result;
int i = 12;
while (pbuf[i] > 0) {
if (result.compareTo("") != 0)
result.concat('.');
for (int x=1; x <= pbuf[i]; x++)
result.concat((char)pbuf[i+x]);
i = i + pbuf[i] + 1;
}
return result;
}
int isLocalDomain(String dn) {
for (int i=0; i < NUM_HOSTS; i++) {
if (dn.equalsIgnoreCase(hosts[i]))
return i;
}
return -1;
}
Defining a Protocol
The first step in designing a protocol is to define what data needs to be
exchanged between the client and the server (or between machines, if you are
building a system in which the traditional roles are of little use such as a peer-topeer system).
Over the next few sections, you will write an implementation of a protocol that
gives basic remote control over the Arduinos digital pins and analog inputs to a
program running on your PC. This protocol is called remote control of [Ar]duino
inputs and outputs (RCDIO).5
The client software will send commands to the Arduino, and in this example the
commands are based on the equivalent functions that you use when programming
an Arduino sketch.
Command
0x01
0x02
0x03
0x04
0x05
0x06
Arduino
Equivalent
Description
pinMode()
digitalWrite()
digitalRead()
analogWrite()
analogRead()
The commands 0x02, 0x03, and 0x05 do not need to send back a response, other
than to say that the command was successful. The commands 0x04 and 0x06
need to return an integer value. Command 0x01 will return a string.
From looking at the commands this protocol supports, you can see that it also
needs to define how pin numbers, high and low, input and output, strings, and
integer values are represented.
Type
Representation/Implementation Notes
Pin number
High/low
Input/output
String
Integer
Sent as a 16-bit unsigned integer two bytes with the leastsignificant byte first.
Now you need to define the format of messages. In this protocol, messages will
be sent from the client to the server, and from the server to the client in the
following format:
Byte
Name
Description
02
Reserved
Opcode
Length
Parameters
This protocol will run on TCP port 80. You should avoid using ports that are
reserved by other protocols unless you are sure that no other software on your
computer is listening for connections on that port number. However, as port 80 is
rarely blocked by firewalls and other security measures it is being used here.
The client opens the connection and sends a command request to the Arduino. If
the server can process the request successfully, it sends back a response
message (opcode 0) with either no data or the return value of the command.
Finally, the server closes the connection.
In the event of an error, the server closes the connection without returning a
response. If the client implements a timeout then it can try sending the request
again after a few seconds.
byte length;
};
Now modify the sketchs loop() function. On the first line of loop(), before an
instance of EthernetClient is created, add this line to declare memory space for an
instance of the cmd structure.
cmd myCmd;
The RCDIO protocol states that the first three characters of any message should
be R, C and D. Check that this is received by adding this if statement before
client.stop();
if (
(myCmd.reserved[0] == 'R') &&
(myCmd.reserved[1] == 'C') &&
(myCmd.reserved[2] == 'D')
) {
// process command requests here
}
The command that the client is asking to run is a byte value in the header
structure myCmd.opcode and can be processed using a switch statement. In
the final sketch (Source Code Arduino Sketch) the implementations of all of the
commands are very similar.To add support for the command 0x03 (the equivalent
of the ArduinosdigitalWrite() function), replace the comment // process command
requests here with this code block:
switch (myCmd.opcode) {
case 3:
while (client.available() < 2);
digitalWrite(client.read(), client.read());
sendResponse(&client);
break;
}
Command 0x03 accepts two bytes from the client, and so the code above waits
until at least two bytes are available in the Ethernet Shields buffer. These are then
read and passed to digitalWrite().
Finally, the code above sends back a default response message with no data. The
full source code contains three forms of the function sendResponse() for use
depending on what type of data (if any) is to be sent back to the client. The
version used by command 0x03 looks like this:
void sendResponse(EthernetClient* client) {
cmd response;
response.reserved[0] = 'R';
response.reserved[1] = 'C';
response.reserved[2] = 'D';
response.opcode = 0;
response.length = 0;
client->write((uint8_t*)&response, sizeof(response));
}
The function accepts a pointer to the instance of the EthernetClient class, and
then declares a instance of the cmd structure. After populating the response
variable, the entire header is sent to the client using a single call to the
EthernetClient class method write().
In the versions of sendResponse() that do return data, the length (in bytes) of the
data is included in the header (response.length) and then the function sends the
data immediately after writing the header.
Once the response is sent, the function returns and the next instruction to be
executed is the call to client.stop(), to close the connection.
In an actual project, you should implement far more error checking than the
example code does. Since RCDIO does not mandate checking for errors, it is
omitted from this project so that you can see how much simpler it is for the
Arduino to work with this protocol than HTTP.
Because Processing does not support structs, if you want to use a similar way of
handling the header information as you have in the Arduino sketch, the cmd
structure can be implemented as a class.
public class cmd {
char[] reserved = new char[3];
byte opcode;
byte oplength;
public cmd() {
}
public cmd(byte[] fromByteArray) {
reserved[0] = (char)fromByteArray[0];
reserved[1] = (char)fromByteArray[1];
reserved[2] = (char)fromByteArray[2];
opcode = fromByteArray[3];
oplength = fromByteArray[4];
}
public byte[] toByteArray() {
byte[] result = new byte[5];
result[0] = (byte)reserved[0];
result[1] = (byte)reserved[1];
result[2] = (byte)reserved[2];
result[3] = opcode;
result[4] = oplength;
return result;
}
}
The additional constructor, which accepts an array of bytes, and the method
toByteArray() are included to make converting a buffer array to a cmd object a
little easier.
Now define a few constants and declare the IP address of the Arduino server:
final int HIGH = 1;
final int LOW = 0;
final int OUTPUT = 1;
final int INPUT = 0;
final String serverIP = "192.168.0.99";
As digitalWrite() does not require a response, the code in this example neglects to
the check the opcode in the response header, and does not check for any data.
Tip: If you want to test the sketch at this point, you will need to add a call
to pinMode() in the Arduno sketch to set the pins you are testing to
output. Once the command 0x02 is supported by both the client and
server, you will be able to change the mode of the pins on the Arduino
from the client.
If you are unsure how the other command functions can be implemented, the full
source code for the Processing sketch is shown in Source Code Processing
Sketch.
if (
(myCmd.reserved[0] == 'R') &&
(myCmd.reserved[1] == 'C') &&
(myCmd.reserved[2] == 'D')
) {
int tmp;
switch (myCmd.opcode) {
case 1:
sendInfo(&client);
break;
case 2:
while (client.available() < 2);
pinMode(client.read(), client.read());
sendResponse(&client);
break;
case 3:
while (client.available() < 2);
digitalWrite(client.read(), client.read());
sendResponse(&client);
break;
case 4:
while (client.available() < 1);
tmp = digitalRead(client.read());
sendResponse(&client, (byte)tmp);
break;
case 5:
while (client.available() < 2);
analogWrite(client.read(), client.read());
sendResponse(&client);
break;
case 6:
while (client.available() < 1);
tmp = analogRead(client.read());
sendResponse(&client, (unsigned int)tmp);
break;
}
}
client.stop();
}
}
void sendInfo(EthernetClient* client) {
char message[] = "RCDIO Version 0.1 - Arduino Uno";
cmd response;
response.reserved[0] = 'R';
response.reserved[1] = 'C';
response.reserved[2] = 'D';
response.opcode = 0;
response.length = strlen(message);
client->write((uint8_t*)&response, sizeof(response));
client->write(message);
}
void sendResponse(EthernetClient* client) {
cmd response;
response.reserved[0] = 'R';
response.reserved[1] = 'C';
response.reserved[2] = 'D';
response.opcode = 0;
response.length = 0;
client->write((uint8_t*)&response, sizeof(response));
}
void sendResponse(EthernetClient* client, byte data) {
cmd response;
response.reserved[0] = 'R';
response.reserved[1] = 'C';
response.reserved[2] = 'D';
response.opcode = 0;
response.length = 1;
client->write((uint8_t*)&response, sizeof(response));
client->write(data);
}
void sendResponse(EthernetClient* client, unsigned int data) {
cmd response;
response.reserved[0] = 'R';
response.reserved[1] = 'C';
response.reserved[2] = 'D';
response.opcode = 0;
response.length = 2;
client->write((uint8_t*)&response, sizeof(response));
client->write((byte)(data & 0x0000FFFF));
client->write((byte)(data >> 8));
}
c.write((byte)pin);
while (c.available() < 5);
byte[] hdr = new byte[5];
c.readBytes(hdr);
cmd response = new cmd(hdr);
if (response.oplength > 0) {
while (c.available() < 1);
result = 0 + c.read();
}
c.stop();
return result;
}
void digitalWrite(int pin, int mode) {
cmd myCmd = new cmd();
myCmd.reserved[0] = 'R';
myCmd.reserved[1] = 'C';
myCmd.reserved[2] = 'D';
myCmd.opcode = 3;
myCmd.oplength = 2;
Client c = new Client(this, serverIP, 80);
c.write(myCmd.toByteArray());
c.write((byte)pin);
if (mode == HIGH)
c.write((byte)1);
else
c.write((byte)0);
while (c.available() < 5);
byte[] hdr = new byte[5];
c.readBytes(hdr);
cmd response = new cmd(hdr);
c.stop();
}
void pinMode(int pin, int mode) {
cmd myCmd = new cmd();
myCmd.reserved[0] = 'R';
myCmd.reserved[1] = 'C';
myCmd.reserved[2] = 'D';
myCmd.opcode = 2;
myCmd.oplength = 2;
Client c = new Client(this, serverIP, 80);
c.write(myCmd.toByteArray());
c.write((byte)pin);
if (mode == HIGH)
c.write((byte)1);
else
c.write((byte)0);
while (c.available() < 5);
byte[] hdr = new byte[5];
c.readBytes(hdr);
cmd response = new cmd(hdr);
if (response.opcode != 0)
println("OK");
c.stop();
}
By now, you should be very familiar with setting the MAC and IP address for how the Arduino connects to
the network.
3
4
Be aware that protocols can be, and often are, reverse engineered. Obscurity is not security.
Processing is a programming language and development environment that is often used by Arduino
programmers. You can download it, for free, at www.processing.org
Abstract
The Hypertext Transfer Protocol (HTTP) is an application-level protocol with the
lightness and speed necessary for distributed, collaborative, hypermedia
information systems. It is a generic, stateless, object-oriented protocol which can
be used for many tasks, such as name servers and distributed object
management systems, through extension of its request methods (commands). A
feature of HTTP is the typing of data representation, allowing systems to be built
independently of the data being transferred.
HTTP has been in use by the World-Wide Web global information initiative since
1990. This specification reflects common usage of the protocol referred to as
HTTP/1.0.
Table of Contents
1. Introduction
2. Notational Conventions and Generic Grammar
3. Protocol Parameters
4. HTTP Message
5. Request
6. Response
7. Entity
8. Method Definitions
9. Status Code Definitions
10. Header Field Definitions
11. Access Authentication
1. Introduction
1.1 Purpose
The Hypertext Transfer Protocol (HTTP) is an application-level protocol with the
lightness and speed necessary for distributed, collaborative, hypermedia
information systems. HTTP has been in use by the World-Wide Web global
information initiative since 1990. This specification reflects common usage of the
protocol referred to as HTTP/1.0. This specification describes the features that
seem to be consistently implemented in most HTTP/1.0 clients and servers. The
specification is split into two sections. Those features of HTTP for which
implementations are usually consistent are described in the main body of this
document. Those features which have few or inconsistent implementations are
listed in Appendix A.D.
Practical information systems require more functionality than simple retrieval,
including search, front-end update, and annotation. HTTP allows an open-ended
set of methods to be used to indicate the purpose of a request. It builds on the
discipline of reference provided by the Uniform Resource Identifier (URI), as a
location (URL) or name (URN), for indicating the resource on which a method is to
be applied. Messages are passed in a format similar to that used by Internet Mail
and the Multipurpose Internet Mail Extensions (MIME).
HTTP is also used as a generic protocol for communication between user agents
and proxies/gateways to other Internet protocols, such as SMTP, NNTP, FTP,
Gopher, and WAIS, allowing basic hypermedia access to resources available from
diverse applications and simplifying the implementation of user agents.
1.2 Terminology
connection
A transport layer virtual circuit established between two application programs for
the purpose of communication.
message
The basic unit of HTTP communication, consisting of a structured sequence of
octets matching the syntax defined in Section 4 and transmitted via the
connection.
request
An HTTP request message (as defined in Section 5).
response
communication, though the tunnel may have been initiated by an HTTP request.
The tunnel ceases to exist when both ends of the relayed connections are
closed. Tunnels are used when a portal is necessary and the intermediary
cannot, or should not, interpret the relayed communication.
cache
A programs local store of response messages and the subsystem that controls
its message storage, retrieval, and deletion. A cache stores cachable responses
in order to reduce the response time and network bandwidth consumption on
future, equivalent requests. Any client or server may include a cache, though a
cache cannot be used by a server while it is acting as a tunnel.
Any given program may be capable of being both a client and a server; our use of
these terms refers only to the role being performed by the program for a particular
connection, rather than to the programs capabilities in general. Likewise, any
server may act as an origin server, proxy, gateway, or tunnel, switching behavior
based on the nature of each request.
A more complicated situation occurs when one or more intermediaries are present
in the request/response chain. There are three common forms of intermediary:
proxy, gateway, and tunnel. A proxy is a forwarding agent, receiving requests for a
URI in its absolute form, rewriting all or parts of the message, and forwarding the
reformatted request toward the server identified by the URI. A gateway is a
receiving agent, acting as a layer above some other server(s) and, if necessary,
translating the requests to the underlying servers protocol. A tunnel acts as a
relay point between two connections without changing the messages; tunnels are
used when the communication needs to pass through an intermediary (such as a
firewall) even when the intermediary cannot understand the contents of the
messages.
On the Internet, HTTP communication generally takes place over TCP/IP
connections. The default port is TCP 80, but other ports can be used. This does
not preclude HTTP from being implemented on top of any other protocol on the
Internet, or on other networks. HTTP only presumes a reliable transport; any
protocol that provides such guarantees can be used, and the mapping of the
HTTP/1.0 request and response structures onto the transport data units of the
protocol in question is outside the scope of this specification.
Except for experimental applications, current practice requires that the connection
be established by the client prior to each request and closed by the server after
sending the response. Both clients and servers should be aware that either party
may close the connection prematurely, due to user action, automated time-out, or
program failure, and should handle such closing in a predictable fashion. In any
case, the closing of the connection by either or both parties always terminates the
current request, regardless of its status.
HTTP/1.0 defines the octet sequence CR LF as the end-of-line marker for all
protocol elements except the Entity-Body (see Appendix A.B for tolerant
applications). The end-of-line marker within an Entity-Body is defined by its
associated media type, as described in Section 3.6.
CRLF = CR LF
HTTP/1.0 headers may be folded onto multiple lines if each continuation line
begins with a space or horizontal tab. All linear whitespace, including folding, has
the same semantics as SP.
LWS = [CRLF] 1*( SP | HT )
However, folding of header lines is not expected by some applications, and should
not be generated by HTTP/1.0 applications.
The TEXT rule is only used for descriptive field contents and values that are not
intended to be interpreted by the message parser. Words of *TEXT may contain
octets from character sets other than US-ASCII.
TEXT = <any OCTET except CTLs,
but including LWS>
Recipients of header field TEXT containing octets outside the US-ASCII character
Many HTTP/1.0 header field values consist of words separated by LWS or special
characters. These special characters must be in a quoted string to be used within
a parameter value.
word = token | quoted-string
token = 1*<any CHAR except CTLs or tspecials>
tspecials = "(" | ")" | "<" | ">" | "@"
| "," | ";" | ":" | "\" | <">
| "/" | "[" | "]" | "?" | "="
| "{" | "}" | SP | HT
3. Protocol Parameters
3.1 HTTP Version
HTTP uses a <major>.<minor> numbering scheme to indicate versions of the
protocol. The protocol versioning policy is intended to allow the sender to indicate
the format of a message and its capacity for understanding further HTTP
communication, rather than the features obtained via that communication. No
change is made to the version number for the addition of message components
which do not affect communication behavior or which only add to extensible field
values. The <minor> number is incremented when the changes made to the
protocol add features which do not change the general message parsing
algorithm, but which may add to the message semantics and imply additional
capabilities of the sender. The <major> number is incremented when the format of
a message within the protocol is changed.
The version of an HTTP message is indicated by an HTTP-Version field in the first
line of the message. If the protocol version is not specified, the recipient must
assume that the message is in the simple HTTP/0.9 format.
HTTP-Version = "HTTP" "/" 1*DIGIT "." 1*DIGIT
Note that the major and minor numbers should be treated as separate integers
and that each may be incremented higher than a single digit. Thus, HTTP/2.4 is a
lower version than HTTP/2.13, which in turn is lower than HTTP/12.3. Leading
zeros should be ignored by recipients and never generated by senders.
This document defines both the 0.9 and 1.0 versions of the HTTP protocol.
Applications sending Full-Request or Full-Response messages, as defined by this
specification, must include an HTTP-Version of HTTP/1.0.
HTTP/1.0 servers must:
recognize the format of the Request-Line for HTTP/0.9 and HTTP/1.0
requests;
understand any valid request in the format of HTTP/0.9 or HTTP/1.0;
respond appropriately with a message in the same protocol version used by
the client.
HTTP/1.0 clients must:
recognize the format of the Status-Line for HTTP/1.0 responses;
understand any valid response in the format of HTTP/0.9 or HTTP/1.0.
Proxy and gateway applications must be careful in forwarding requests that are
received in a format different than that of the applications native HTTP version.
Since the protocol version indicates the protocol capability of the sender, a
proxy/gateway must never send a message with a version indicator which is
greater than its native version; if a higher version request is received, the
proxy/gateway must either downgrade the request version or respond with an
error. Requests with a version lower than that of the applications native format
may be upgraded before being forwarded; the proxy/gateways response to that
request must follow the server requirements listed above.
For definitive information on URL syntax and semantics, see RFC 1738 and RFC
1808. The BNF above includes national characters not allowed in valid URLs as
specified by RFC 1738, since HTTP servers are not restricted in the set of
unreserved characters allowed to represent the rel_path part of addresses, and
HTTP proxies may receive requests for URIs not defined by RFC 1738.
3.2.2 http URL
The http scheme is used to locate network resources via the HTTP protocol.
This section defines the scheme-specific syntax and semantics for http URLs.
http_URL = "http:" "//" host [ ":" port ] [ abs_path ]
host = <A legal Internet host domain name
or IP address (in dotted-decimal form),
as defined by Section 2.1 of RFC 1123>
port = *DIGIT
If the port is empty or not given, port 80 is assumed. The semantics are that the
identified resource is located at the server listening for TCP connections on that
port of that host, and the Request-URI for the resource is abs_path. If the
abs_path is not present in the URL, it must be given as / when used as a
Request-URI (Section 5.1.2).
The canonical form for http URLs is obtained by converting any UPALPHA
characters in host to their LOALPHA equivalent (hostnames are case-insensitive),
eliding the [ : port ] if the port is 80, and replacing an empty abs_path with /.
| token
Although HTTP allows an arbitrary token to be used as a charset value, any token
that has a predefined value within the IANA Character Set registry must represent
the character set defined by that registry. Applications should limit their use of
character sets to those defined by the IANA registry.
The character set of an entity body should be labelled as the lowest common
denominator of the character codes used within that body, with the exception that
no label is preferred over the labels US-ASCII or ISO-8859-1.
Note: For future compatibility, HTTP/1.0 applications should consider gzip and
compress to be equivalent to x-gzip and x-compress, respectively.
All content-coding values are case-insensitive. HTTP/1.0 uses content-coding
values in the Content-Encoding (Section 10.3) header field. Although the value
describes the content-coding, what is more important is that it indicates what
decoding mechanism will be required to remove the encoding. Note that a single
program may be capable of decoding multiple content-coding formats.
parameter as part of the media type value. The message body is itself a protocol
element and must therefore use only CRLF to represent line breaks between
body-parts. Multipart body-parts may contain HTTP header fields which are
significant to the meaning of that part.
Examples:
User-Agent: CERN-LineMode/2.15 libwww/2.17b3
Server: Apache/0.8.4
Product tokens should be short and to the point use of them for advertizing or
other non-essential information is explicitly forbidden. Although any token
character may appear in a product-version, this token should only be used for a
version identifier (i.e., successive versions of the same product should only differ
in the product-version portion of the product value).
4. HTTP Message
4.1 Message Types
HTTP messages consist of requests from client to server and responses from
server to client.
HTTP-message = Simple-Request ; HTTP/0.9 messages
| Simple-Response
| Full-Request ; HTTP/1.0 messages
| Full-Response
Full-Request and Full-Response use the generic message format of RFC 822 for
transferring entities. Both messages may include optional header fields (also
known as headers) and an entity body. The entity body is separated from the
headers by a null line (i.e., a line with nothing preceding the CRLF).
Full-Request = Request-Line ; Section 5.1
*( General-Header ; Section 4.3
| Request-Header ; Section 5.2
| Entity-Header ) ; Section 7.1
CRLF
[ Entity-Body ] ; Section 7.2
Full-Response = Status-Line ; Section 6.1
*( General-Header ; Section 4.3
| Response-Header ; Section 6.2
| Entity-Header ) ; Section 7.1
CRLF
[ Entity-Body ] ; Section 7.2
The order in which header fields are received is not significant. However, it is
good practice to send General-Header fields first, followed by Request-Header
or Response-Header fields prior to the Entity-Header fields.
Multiple HTTP-header fields with the same field-name may be present in a
message if and only if the entire field-value for that header field is defined as a
comma-separated list [i.e., #(values)]. It must be possible to combine the multiple
header fields into one field-name: field-value pair, without changing the
semantics of the message, by appending each subsequent field-value to the first,
each separated by a comma.
General header field names can be extended reliably only in combination with a
change in the protocol version. However, new or experimental header fields may
be given the semantics of general header fields if all parties in the communication
recognize them to be general header fields. Unrecognized header fields are
treated as Entity-Header fields.
5. Request
A request message from a client to a server includes, within the first line of that
message, the method to be applied to the resource, the identifier of the resource,
and the protocol version in use. For backwards compatibility with the more limited
HTTP/0.9 protocol, there are two valid formats for an HTTP request:
Request = Simple-Request | Full-Request
Simple-Request = "GET" SP Request-URI CRLF
Full-Request = Request-Line ; Section 5.1
*( General-Header ; Section 4.3
| Request-Header ; Section 5.2
| Entity-Header ) ; Section 7.1
CRLF
[ Entity-Body ] ; Section 7.2
5.1 Request-Line
The Request-Line begins with a method token, followed by the Request-URI and
the protocol version, and ending with CRLF. The elements are separated by SP
characters. No CR or LF are allowed except in the final CRLF sequence.
Request-Line = Method SP Request-URI SP HTTP-Version CRLF
5.1.1 Method
The Method token indicates the method to be performed on the resource
identified by the Request-URI. The method is case-sensitive.
Method = "GET" ; Section 8.1
| "HEAD" ; Section 8.2
| "POST" ; Section 8.3
| extension-method
extension-method = token
The Request-URI is a Uniform Resource Identifier (Section 3.2) and identifies the
resource upon which to apply the request.
Request-URI = absoluteURI | abs_path
The two options for Request-URI are dependent on the nature of the request.
The absoluteURI form is only allowed when the request is being made to a proxy.
The proxy is requested to forward the request and return the response. If the
request is GET or HEAD and a prior response is cached, the proxy may use the
cached message if it passes any restrictions in the Expires header field.
Note that the proxy may forward the request on to another proxy or directly to the
server specified by the absoluteURI. In order to avoid request loops, a proxy must
be able to recognize all of its server names, including any aliases, local variations,
and the numeric IP address. An example Request-Line would be:
GET /TheProject.html HTTP/1.0
followed by the remainder of the Full-Request. Note that the absolute path cannot
be empty; if none is present in the original URI, it must be given as / (the server
root).
The Request-URI is transmitted as an encoded string, where some characters
may be escaped using the % HEX HEX encoding defined by RFC 1738. The
origin server must decode the Request-URI in order to properly interpret the
request.
6. Response
After receiving and interpreting a request message, a server responds in the form
of an HTTP response message.
Response = Simple-Response | Full-Response
Simple-Response = [ Entity-Body ]
Full-Response = Status-Line ; Section 6.1
*( General-Header ; Section 4.3
| Response-Header ; Section 6.2
| Entity-Header ) ; Section 7.1
CRLF
[ Entity-Body ] ; Section 7.2
A Simple-Response should only be sent in response to an HTTP/0.9 SimpleRequest or if the server only supports the more limited HTTP/0.9 protocol. If a
client sends an HTTP/1.0 Full-Request and receives a response that does not
begin with a Status-Line, it should assume that the response is a SimpleResponse and parse it accordingly. Note that the Simple-Response consists only
of the entity body and is terminated by the server closing the connection.
6.1 Status-Line
The first line of a Full-Response message is the Status-Line, consisting of the
protocol version followed by a numeric status code and its associated textual
phrase, with each element separated by SP characters. No CR or LF is allowed
except in the final CRLF sequence.
Status-Line = HTTP-Version SP Status-Code SP Reason-Phrase CRLF
Since a status line always begins with the protocol version and status code
"HTTP/" 1*DIGIT "." 1*DIGIT SP 3DIGIT SP
The first digit of the Status-Code defines the class of response. The last two digits
do not have any categorization role. There are 5 values for the first digit:
1xx: Informational - Not used, but reserved for future use.
2xx: Success - The action was successfully received, understood, and accepted.
3xx: Redirection - Further action must be taken in order to complete the request.
4xx: Client Error - The request contains bad syntax or cannot be fulfilled.
5xx: Server Error - The server failed to fulfill an apparently valid request.
HTTP status codes are extensible, but the above codes are the only ones
generally recognized in current practice. HTTP applications are not required to
understand the meaning of all registered status codes, though such
understanding is obviously desirable. However, applications must understand the
class of any status code, as indicated by the first digit, and treat any unrecognized
response as being equivalent to the x00 status code of that class, with the
exception that an unrecognized response must not be cached.
7. Entity
Full-Request and Full-Response messages may transfer an entity within some
requests and responses. An entity consists of Entity-Header fields and (usually)
an Entity-Body. In this section, both sender and recipient refer to either the client
or the server, depending on who sends and who receives the entity.
An entity body is included with a request message only when the request method
calls for one. The presence of an entity body in a request is signaled by the
inclusion of a Content-Length header field in the request message headers.
HTTP/1.0 requests containing an entity body must include a valid Content-Length
header field.
For response messages, whether or not an entity body is included with a
message is dependent on both the request method and the response code. All
responses to the HEAD request method must not include a body, even though the
presence of entity header fields may lead one to believe they do. All 1xx
(informational), 204 (no content), and 304 (not modified) responses must not
include a body. All other responses must include an entity body or a ContentLength header field defined with a value of zero (0).
7.2.1 Type
When an Entity-Body is included with a message, the data type of that body is
determined via the header fields Content-Type and Content-Encoding. These
define a two-layer, ordered encoding model:
entity-body := Content-Encoding( Content-Type( data ) )
A Content-Type specifies the media type of the underlying data. A ContentEncoding may be used to indicate any additional content coding applied to the
type, usually for the purpose of data compression, that is a property of the
resource requested. The default for the content encoding is none (i.e., the identity
function).
Any HTTP/1.0 message containing an entity body should include a Content-Type
header field defining the media type of that body. If and only if the media type is
not given by a Content-Type header, as is the case for Simple-Response
messages, the recipient may attempt to guess the media type via inspection of its
content and/or the name extension(s) of the URL used to identify the resource. If
the media type remains unknown, the recipient should treat it as type
application/octet-stream.
7.2.2 Length
When an Entity-Body is included with a message, the length of that body may be
determined in one of two ways. If a Content-Length header field is present, its
value in bytes represents the length of the Entity-Body. Otherwise, the body length
is determined by the closing of the connection by the server.
Closing the connection cannot be used to indicate the end of a request body,
since it leaves no possibility for the server to send back a response. Therefore,
HTTP/1.0 requests containing an entity body must include a valid Content-Length
header field. If a request contains an entity body and Content-Length is not
specified, and the server does not recognize or cannot calculate the length from
other fields, then the server should send a 400 (bad request) response.
8. Method Definitions
The set of common methods for HTTP/1.0 is defined below. Although this set can
be expanded, additional methods cannot be assumed to share the same
semantics for separately extended clients and servers.
8.1 GET
The GET method means retrieve whatever information (in the form of an entity) is
identified by the Request-URI. If the Request-URI refers to a data-producing
process, it is the produced data which shall be returned as the entity in the
response and not the source text of the process, unless that text happens to be
the output of the process.
The semantics of the GET method changes to a conditional GET if the request
message includes an If-Modified-Since header field. A conditional GET method
requests that the identified resource be transferred only if it has been modified
since the date given by the If-Modified-Since header, as described in Section
10.9. The conditional GET method is intended to reduce network usage by
allowing cached entities to be refreshed without requiring multiple requests or
transferring unnecessary data.
8.2 HEAD
The HEAD method is identical to GET except that the server must not return any
Entity-Body in the response. The metainformation contained in the HTTP headers
in response to a HEAD request should be identical to the information sent in
response to a GET request. This method can be used for obtaining
metainformation about the resource identified by the Request-URI without
transferring the Entity-Body itself. This method is often used for testing hypertext
links for validity, accessibility, and recent modification.
There is no conditional HEAD request analogous to the conditional GET. If an IfModified-Since header field is included with a HEAD request, it should be ignored.
8.3 POST
The POST method is used to request that the destination server accept the entity
enclosed in the request as a new subordinate of the resource identified by the
Request-URI in the Request-Line. POST is designed to allow a uniform method to
cover the following functions:
disallowed when processing actually takes place. There is no facility for resending a status code from an asynchronous operation such as this.
The 202 response is intentionally non-committal. Its purpose is to allow a server to
accept a request for some other process (perhaps a batch-oriented process that is
only run once per day) without requiring that the user agents connection to the
server persist until the process is completed. The entity returned with this
response should include an indication of the requests current status and either a
pointer to a status monitor or some estimate of when the user can expect the
request to be fulfilled.
204 No Content
The server has fulfilled the request but there is no new information to send back. If
the client is a user agent, it should not change its document view from that which
caused the request to be generated. This response is primarily intended to allow
input for scripts or other actions to take place without causing a change to the
user agents active document view. The response may include new
metainformation in the form of entity headers, which should apply to the document
currently in the user agents active view.
The requested resource has been assigned a new permanent URL and any future
references to this resource should be done using that URL. Clients with link
editing capabilities should automatically relink references to the Request-URI to
the new reference returned by the server, where possible.
The new URL must be given by the Location field in the response. Unless it was a
HEAD request, the Entity-Body of the response should contain a short note with a
hyperlink to the new URL.
If the 301 status code is received in response to a request using the POST
method, the user agent must not automatically redirect the request unless it can
be confirmed by the user, since this might change the conditions under which the
request was issued.
302 Moved Temporarily
The requested resource resides temporarily under a different URL. Since the
redirection may be altered on occasion, the client should continue to use the
Request-URI for future requests.
The URL must be given by the Location field in the response. Unless it was a
HEAD request, the Entity-Body of the response should contain a short note with a
hyperlink to the new URI(s).
If the 302 status code is received in response to a request using the POST
method, the user agent must not automatically redirect the request unless it can
be confirmed by the user, since this might change the conditions under which the
request was issued.
304 Not Modified
If the client has performed a conditional GET request and access is allowed, but
the document has not been modified since the date and time specified in the IfModified-Since field, the server must respond with this status code and not send
an Entity-Body to the client. Header fields contained in the response should only
include information which is relevant to cache managers or which may have
changed independently of the entitys Last-Modified date. Examples of relevant
header fields include: Date, Server, and Expires. A cache should update its
cached entity to reflect any new field values given in the 304 response.
10.1 Allow
The Allow entity-header field lists the set of methods supported by the resource
identified by the Request-URI. The purpose of this field is strictly to inform the
recipient of valid methods associated with the resource. The Allow header field is
not permitted in a request using the POST method, and thus should be ignored if
it is received as part of a POST entity.
Allow = "Allow" ":" 1#method
Example of use:
Allow: GET, HEAD
This field cannot prevent a client from trying other methods. However, the
indications given by the Allow header field value should be followed. The actual
set of allowed methods is defined by the origin server at the time of each request.
A proxy must not modify the Allow header field even if it does not understand all
the methods specified, since the user agent may have other means of
communicating with the origin server.
The Allow header field does not indicate what methods are implemented by the
server.
10.2 Authorization
A user agent that wishes to authenticate itself with a serverusually, but not
necessarily, after receiving a 401 responsemay do so by including an
Authorization request-header field with the request. The Authorization field value
consists of credentials containing the authentication information of the user agent
for the realm of the resource being requested.
Authorization = "Authorization" ":" credentials
10.3 Content-Encoding
The Content-Encoding entity-header field is used as a modifier to the media-type.
When present, its value indicates what additional content coding has been applied
to the resource, and thus what decoding mechanism must be applied in order to
obtain the media-type referenced by the Content-Type header field. The ContentEncoding is primarily used to allow a document to be compressed without losing
the identity of its underlying media type.
Content-Encoding = "Content-Encoding" ":" content-coding
10.4 Content-Length
The Content-Length entity-header field indicates the size of the Entity-Body, in
decimal number of octets, sent to the recipient or, in the case of the HEAD
method, the size of the Entity-Body that would have been sent had the request
been a GET.
Content-Length = "Content-Length" ":" 1*DIGIT
An example is
Content-Length: 3495
Applications should use this field to indicate the size of the Entity-Body to be
transferred, regardless of the media type of the entity. A valid Content-Length field
value is required on all HTTP/1.0 request messages containing an entity body.
Any Content-Length greater than or equal to zero is a valid value. Section 7.2.2
describes how to determine the length of a response entity body if a ContentLength is not given.
Note: The meaning of this field is significantly different from the corresponding
definition in MIME, where it is an optional field used within the message/externalbody content-type. In HTTP, it should be used whenever the entitys length can
be determined prior to being transferred.
10.5 Content-Type
The Content-Type entity-header field indicates the media type of the Entity-Body
sent to the recipient or, in the case of the HEAD method, the media type that
10.6 Date
The Date general-header field represents the date and time at which the message
was originated, having the same semantics as orig-date in RFC 822. The field
value is an HTTP-date, as described in Section 3.3.
Date = "Date" ":" HTTP-date
An example is
Date: Tue, 15 Nov 1994 08:12:31 GMT
If a message is received via direct connection with the user agent (in the case of
requests) or the origin server (in the case of responses), then the date can be
assumed to be the current date at the receiving end. However, since the dateas
it is believed by the originis important for evaluating cached responses, origin
servers should always include a Date header. Clients should only send a Date
header field in messages that include an entity body, as in the case of the POST
request, and even then it is optional. A received message which does not have a
Date header field should be assigned one by the recipient if the message will be
cached by that recipient or gatewayed via a protocol which requires a Date.
In theory, the date should represent the moment just before the entity is
generated. In practice, the date can be generated at any time during the message
origination without affecting its semantic value.
10.7 Expires
The Expires entity-header field gives the date/time after which the entity should be
considered stale. This allows information providers to suggest the volatility of the
resource, or a date after which the information may no longer be valid.
Applications must not cache this entity beyond the date given. The presence of an
Expires field does not imply that the original resource will change or cease to exist
at, before, or after that time. However, information providers that know or even
suspect that a resource will change by a certain date should include an Expires
header with that date. The format is an absolute date and time as defined by
HTTP-date in Section 3.3.
Expires = "Expires" ":" HTTP-date
If the date given is equal to or earlier than the value of the Date header, the
recipient must not cache the enclosed entity. If a resource is dynamic by nature,
as is the case with many data-producing processes, entities from that resource
should be given an appropriate Expires value which reflects that dynamism.
The Expires field cannot be used to force a user agent to refresh its display or
reload a resource; its semantics apply only to caching mechanisms, and such
mechanisms need only check a resources expiration status when a new request
for that resource is initiated.
User agents often have history mechanisms, such as Back buttons and history
lists, which can be used to redisplay an entity retrieved earlier in a session. By
default, the Expires field does not apply to history mechanisms. If the entity is still
in storage, a history mechanism should display it even if the entity has expired,
unless the user has specifically configured the agent to refresh expired history
documents.
Note: Applications are encouraged to be tolerant of bad or misinformed
implementations of the Expires header. A value of zero (0) or an invalid date
format should be considered equivalent to an expires immediately. Although
these values are not legitimate for HTTP/1.0, a robust implementation is always
desirable.
10.8 From
The From request-header field, if given, should contain an Internet e-mail address
for the human user who controls the requesting user agent. The address should
be machine-usable, as defined by mailbox in RFC 822 (as updated by RFC 1123):
From = "From" ":" mailbox
An example is:
From: [email protected]
This header field may be used for logging purposes and as a means for identifying
the source of invalid or unwanted requests. It should not be used as an insecure
form of access protection. The interpretation of this field is that the request is
being performed on behalf of the person given, who accepts responsibility for the
method performed. In particular, robot agents should include this header so that
the person responsible for running the robot can be contacted if problems occur
on the receiving end.
The Internet e-mail address in this field may be separate from the Internet host
which issued the request. For example, when a request is passed through a
proxy, the original issuers address should be used.
10.9 If-Modified-Since
The If-Modified-Since request-header field is used with the GET method to make it
conditional: if the requested resource has not been modified since the time
specified in this field, a copy of the resource will not be returned from the server;
instead, a 304 (not modified) response will be returned without any Entity-Body.
If-Modified-Since = "If-Modified-Since" ":" HTTP-date
10.10 Last-Modified
The Last-Modified entity-header field indicates the date and time at which the
sender believes the resource was last modified. The exact semantics of this field
are defined in terms of how the recipient should interpret it: if the recipient has a
copy of this resource which is older than the date given by the Last-Modified field,
that copy should be considered stale.
Last-Modified = "Last-Modified" ":" HTTP-date
The exact meaning of this header field depends on the implementation of the
sender and the nature of the original resource. For files, it may be just the file
system last-modified time. For entities with dynamically included parts, it may be
the most recent of the set of last-modify times for its component parts. For
database gateways, it may be the last-update timestamp of the record. For virtual
objects, it may be the last time the internal state changed.
An origin server must not send a Last-Modified date which is later than the
servers time of message origination. In such cases, where the resources last
modification would indicate some time in the future, the server must replace that
date with the message origination date.
10.11 Location
The Location response-header field defines the exact location of the resource that
was identified by the Request-URI. For 3xx responses, the location must indicate
the servers preferred URL for automatic redirection to the resource. Only one
absolute URL is allowed.
Location = "Location" ":" absoluteURI
An example is
Location: http://www.w3.org/hypertext/WWW/NewLocation.html
10.12 Pragma
The Pragma general-header field is used to include implementation-specific
directives that may apply to any recipient along the request/response chain. All
pragma directives specify optional behavior from the viewpoint of the protocol;
however, some systems may require that behavior be consistent with the
directives.
Pragma = "Pragma" ":" 1#pragma-directive
pragma-directive = "no-cache" | extension-pragma
extension-pragma = token [ "=" word ]
10.13 Referer
The Referer request-header field allows the client to specify, for the servers
benefit, the address (URI) of the resource from which the Request-URI was
obtained. This allows a server to generate lists of back-links to resources for
interest, logging, optimized caching, etc. It also allows obsolete or mistyped links
to be traced for maintenance. The Referer field must not be sent if the RequestURI was obtained from a source that does not have its own URI, such as input
from the user keyboard.
Referer = "Referer" ":" ( absoluteURI | relativeURI )
Example:
Referer: http://www.w3.org/hypertext/DataSources/Overview.html
10.14 Server
The Server response-header field contains information about the software used
by the origin server to handle the request. The field can contain multiple product
tokens (Section 3.7) and comments identifying the server and any significant
subproducts. By convention, the product tokens are listed in order of their
significance for identifying the application.
Server = "Server" ":" 1*( product | comment )
Example:
Server: CERN/3.0 libwww/2.17
If the response is being forwarded through a proxy, the proxy application must not
add its data to the product list.
10.15 User-Agent
The User-Agent request-header field contains information about the user agent
originating the request. This is for statistical purposes, the tracing of protocol
violations, and automated recognition of user agents for the sake of tailoring
responses to avoid particular user agent limitations. Although it is not required,
user agents should include this field with requests. The field can contain multiple
product tokens (Section 3.7) and comments identifying the agent and any
subproducts which form a significant part of the user agent. By convention, the
product tokens are listed in order of their significance for identifying the
application.
User-Agent = "User-Agent" ":" 1*( product | comment )
Example:
User-Agent: CERN-LineMode/2.15 libwww/2.17b3
Note: Some current proxy applications append their product information to the list
in the User-Agent field. This is not recommended, since it makes machine
interpretation of these fields ambiguous.
10.16 WWW-Authenticate
The WWW-Authenticate response-header field must be included in 401
(unauthorized) response messages. The field value consists of at least one
challenge that indicates the authentication scheme(s) and parameters applicable
to the Request-URI.
WWW-Authenticate = "WWW-Authenticate" ":" 1#challenge
The HTTP access authentication process is described in Section 11. User agents
must take special care in parsing the WWW-Authenticate field value if it contains
more than one challenge, or if more than one WWW-Authenticate header field is
provided, since the contents of a challenge may itself contain a comma-separated
list of authentication parameters.
The domain over which credentials can be automatically applied by a user agent
is determined by the protection space. If a prior request has been authorized, the
same credentials may be reused for all other requests within that protection space
for a period of time determined by the authentication scheme, parameters, and/or
user preference. Unless otherwise defined by the authentication scheme, a single
protection space cannot extend outside the scope of its server.
If the server does not wish to accept the credentials sent with a request, it should
return a 403 (forbidden) response.
The HTTP protocol does not restrict applications to this simple challenge-
where WallyWorld is the string assigned by the server to identify the protection
space of the Request-URI.
To receive authorization, the client sends the user-ID and password, separated by
a single colon (:) character, within a base64 encoded string in the credentials.
basic-credentials = "Basic" SP basic-cookie
basic-cookie = <base64 [5] encoding of userid-password,
except not limited to 76 char/line>
userid-password = [ token ] ":" *TEXT
If the user agent wishes to send the user-ID Aladdin and password open
sesame, it would use the following header field:
Authorization: Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==
Table of Contents
2. Introduction
3. Domain Name Space and RR Definitions
4. Messages
5. Master Files
6. Name Server Implementation
2. Introduction
2.1 Overview
The goal of domain names is to provide a mechanism for naming resources in
such a way that the names are usable in different hosts, networks, protocol
families, internets, and administrative organizations.
From the users point of view, domain names are useful as arguments to a local
agent, called a resolver, which retrieves information associated with the domain
name. Thus a user might ask for the host address or mail information associated
with a particular domain name. To enable the user to request a particular type of
information, an appropriate query type is passed to the resolver with the domain
name. To the user, the domain tree is a single information space; the resolver is
responsible for hiding the distribution of data among name servers from the user.
From the resolvers point of view, the database that makes up the domain space
is distributed among various name servers. Different parts of the domain space
are stored in different name servers, although a particular data item will be stored
redundantly in two or more name servers. The resolver starts with knowledge of at
least one name server. When the resolver processes a user query it asks a known
name server for the information; in return, the resolver either receives the desired
information or a referral to another name server. Using these referrals, resolvers
learn the identities and contents of other name servers. Resolvers are responsible
for dealing with the distribution of the domain space and dealing with the effects of
name server failure by consulting redundant databases in other servers.
Name servers manage two kinds of data. The first kind of data held in sets called
zones; each zone is the complete database for a particular pruned subtree of the
domain space. This data is called authoritative. A name server periodically checks
to make sure that its zones are up to date, and if not, obtains a new copy of
updated zones from master files stored locally or in another name server. The
second kind of data is cached data which was acquired by a local resolver. This
data may be incomplete, but improves the performance of the retrieval process
when non-local data is repeatedly accessed. Cached data is eventually discarded
by a timeout mechanism.
This functional structure isolates the problems of user interface, failure recovery,
and distribution in the resolvers and isolates the database update and refresh
problems in the name servers.
depending on whether the host runs programs that retrieve information from the
domain system, name servers that answer queries from other hosts, or various
combinations of both functions. The simplest, and perhaps most typical,
configuration is shown below:
User programs interact with the domain name space through resolvers; the format
of user queries and user responses is specific to the host and its operating
system. User queries will typically be operating system calls, and the resolver and
its cache will be part of the host operating system. Less capable hosts may
choose to implement the resolver as a subroutine to be linked in with every
program that needs its services. Resolvers answer user queries with information
they acquire via queries to foreign name servers and the local cache.
Note that the resolver may have to make several queries to several different
foreign name servers to answer a particular user query, and hence the resolution
of a user query may involve several network accesses and an arbitrary amount of
time. The queries to foreign name servers and the corresponding responses have
a standard format described in this memo, and may be datagrams.
Depending on its capabilities, a name server could be a stand alone program on a
dedicated machine or a process or processes on a large timeshared host. A
simple configuration might be:
Here a primary name server acquires information about one or more zones by
reading master files from its local file system, and answers queries about those
zones that arrive from foreign resolvers.
The DNS requires that all zones be redundantly supported by more than one
name server. Designated secondary servers can acquire zones and check for
updates from the primary server using the zone transfer protocol of the DNS. This
configuration is shown below:
The shared database holds domain space data for the local name server and
resolver. The contents of the shared database will typically be a mixture of
authoritative data maintained by the periodic refresh operations of the name
server and cached data from previous resolver requests. The structure of the
domain data and the necessity for synchronization between name servers and
resolvers imply the general characteristics of this database, but the actual format
is up to the local implementor.
Information flow can also be tailored so that a group of hosts act together to
optimize activities. Sometimes this is done to offload less capable hosts so that
they do not have to implement a full resolver. This can be appropriate for PCs or
hosts which want to minimize the amount of new network code which is required.
This scheme can also allow a group of hosts can share a small number of caches
rather than maintaining a large number of separate caches, on the premise that
the centralized caches will have a higher hit ratio. In either case, resolvers are
replaced with stub resolvers which act as front ends to resolvers located in a
recursive server in one or more name servers known to perform that service.
In any case, note that domain components are always replicated for reliability
whenever possible.
2.3 Conventions
The domain system has several conventions dealing with low-level, but
fundamental, issues. While the implementor is free to violate theseconventions
WITHIN HIS OWN SYSTEM, he must observe these conventions in ALL behavior
observed from other hosts.
2.3.1 Preferred name syntax
The DNS specifications attempt to be as general as possible in the rules for
constructing domain names. The idea is that the name of any existing object can
be expressed as a domain name with minimal changes. However, when assigning
a domain name for an object, the prudent user will select a name which satisfies
both the rules of the domain system and any existing rules for the object, whether
these rules are published or implied by existing programs.
For example, when naming a mail domain, the user should satisfy both the rules
of this memo and those in RFC-822. When creating a new host name, the old
rules for HOSTS.TXT should be followed. This avoids problems when old software
is converted to use domain names.
The following syntax will result in fewer problems with many applications that use
domain names (e.g., mail, TELNET).
<domain> ::= <subdomain> | " "
<subdomain> ::= <label> | <subdomain> "." <label>
<label> ::= <letter> [ [ <ldh-str> ] <let-dig> ]
<ldh-str> ::= <let-dig-hyp> | <let-dig-hyp> <ldh-str>
<let-dig-hyp> ::= <let-dig> | "-"
<let-dig> ::= <letter> | <digit>
<letter> ::= any one of the 52 alphabetic characters A
through Z in upper case and a through z in
lower case
<digit> ::= any one of the ten digits 0 through 9
Note that while upper and lower case letters are allowed in domain names, no
significance is attached to the case. That is, two names with the same spelling but
different case are to be treated as if identical.
The labels must follow the rules for ARPANET host names. They must start with a
letter, end with a letter or digit, and have as interior characters only letters, digits,
and hyphen. There are also some restrictions on the length. Labels must be 63
characters or less.
2.3.2 Data Transmission Order
The order of transmission of the header and data described in this document is
resolved to the octet level. Whenever a diagram shows a group of octets, the
order of transmission of those octets is the normal order in which they are read in
English. For example, in the following diagram, the octets are transmitted in the
order they are numbered.
0 1
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| 1 | 2 |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| 3 | 4 |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| 5 | 6 |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
Whenever an octet represents a numeric quantity, the left most bit in the diagram
is the high order or most significant bit. That is, the bit labeled 0 is the most
significant bit. For example, the following diagram represents the value 170
(decimal).
0 1 2 3 4 5 6 7
+-+-+-+-+-+-+-+-+
|1 0 1 0 1 0 1 0|
+-+-+-+-+-+-+-+-+
Similarly, whenever a multi-octet field represents a numeric quantity the left most
bit of the whole field is the most significant bit. When a multi-octet quantity is
transmitted the most significant octet is transmitted first.
2.3.3 Character Case
For all parts of the DNS that are part of the official protocol, all comparisons
between character strings (e.g., labels, domain names, etc.) are done in a caseinsensitive manner. At present, this rule is in force throughout the domain system
without exception. However, future additions beyond current usage may need to
use the full binary octet capabilities in names, so attempts to store domain names
in 7-bit ASCII or use of special bytes to terminate labels, etc., should be avoided.
When data enters the domain system, its original case should be preserved
whenever possible. In certain circumstances this cannot be done. For example, if
two RRs are stored in a database, one at x.y and one at X.Y, they are actually
stored at the same place in the database, and hence only one casing would be
preserved. The basic rule is that case can be discarded only when data is used to
define structure in a database, and two names are identical when compared in a
case insensitive manner.
Loss of case sensitive data must be minimized. Thus while data for x.y and X.Y
may both be stored under a single location x.y or X.Y, data for a.x and B.X would
never be stored under A.x, A.X, b.x, or b.X. In general, this preserves the case of
the first label of a domain name, but forces standardization of interior node labels.
Systems administrators who enter data into the domain database should take
care to represent the data they supply to the domain system in a case-consistent
manner if their system is case-sensitive. The data distribution system in the
domain system will ensure that consistent representations are preserved.
2.3.4 Size limits
Various objects and parameters in the DNS have size limits. They are listed
below. Some could be easily changed, others are more fundamental.
labels
63 octets or less
names
255 octets or less
TTL
positive values of a signed 32 bit number.
UDP messages
512 octets or less
3.2 RR Definitions
3.2.1 Format
All RRs have the same top level format shown below:
1 1 1 1 1 1
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
/ NAME /
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
| TYPE |
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
| CLASS |
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
| TTL |
| |
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
| RDLENGTH |
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--|
/ RDATA /
/ /
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
where:
NAME
an owner name, i.e., the name of the node to which this resource record
pertains.
TYPE
two octets containing one of the RR TYPE codes.
CLASS
two octets containing one of the RR CLASS codes.
TTL
a 32 bit signed integer that specifies the time interval that the resource record
may be cached before the source of the information should again be consulted.
Zero values are interpreted to mean that the RR can only be used for the
transaction in progress, and should not be cached. For example, SOA records
are always distributed with a zero TTL to prohibit caching. Zero values can also
be used for extremely volatile data.
RDLENGTH
an unsigned 16 bit integer that specifies the length in octets of the RDATA field.
RDATA
a variable length string of octets that describes the resource. The format of this
information varies according to the TYPE and CLASS of the resource record.
3.2.2 TYPE values
TYPE fields are used in resource records. Note that these types are a subset of
QTYPEs.
TYPE
Value
Meaning
a host address
NS
MD
MF
CNAME
SOA
MB
MG
MR
NULL
10
a null RR (EXPERIMENTAL)
WKS
11
PTR
12
HINFO
13
host information
MINFO
14
MX
15
mail exchange
TXT
16
text strings
Value
Meaning
AXFR
252
MAILB
253
MAILA
254
255
Value
Meaning
IN
the Internet
CS
CH
HS
Value
Meaning
255
any class
use is for protocols such as FTP that can use special procedures when talking
between machines or operating systems of the same type.
3.3.3 MB RDATA format (EXPERIMENTAL)
MADNAME
A <domain-name> which specifies a host which has the specified mailbox.
MB records cause additional section processing which looks up an A type RRs
corresponding to MADNAME.
3.3.4 MD RDATA format (Obsolete)
MADNAME
A <domain-name> which specifies a host which has a mail agent for the domain
which should be able to deliver mail for the domain.
MD records cause additional section processing which looks up an A type record
corresponding to MADNAME.
MD is obsolete. See the definition of MX and [RFC-974] for details of the new
scheme. The recommended policy for dealing with MD RRs found in a master file
is to reject them, or to convert them to MX RRs with a preference of 0.
3.3.5 MF RDATA format (Obsolete)
MADNAME
A <domain-name> which specifies a host which has a mail agent for the domain
which will accept mail for forwarding to the domain.
MF records cause additional section processing which looks up an A type record
corresponding to MADNAME.
MF is obsolete. See the definition of MX and [RFC-974] for details of the new
scheme. The recommended policy for dealing with MD RRs found in a master file
is to reject them, or to convert them to MX RRs with a preference of 10.
3.3.6 MG RDATA format (EXPERIMENTAL)
MGMNAME
A <domain-name> which specifies a mailbox which is a member of the mail
group specified by the domain name.
MG records cause no additional section processing.
NULL records cause no additional section processing. NULL RRs are not allowed
in master files. NULLs are used as placeholders in some experimental extensions
of the DNS.
3.3.11 NS RDATA format
NSDNAME
A <domain-name> which specifies a host which should be authoritative for the
specified class and domain.
NS records cause both the usual additional section processing to locate a type A
record, and, when used in a referral, a special search of the zone in which they
reside for glue information.
The NS RR states that the named host should be expected to have a zone
starting at owner name of the specified class. Note that the class may not indicate
the protocol family which should be used to communicate with the host, although
it is typically a strong hint. For example, hosts which are name servers for either
Internet (IN) or Hesiod (HS) class information are normally queried using IN class
protocols.
3.3.12 PTR RDATA format
PTRDNAME
A <domain-name> which points to some location in the domain name space.
PTR records cause no additional section processing. These RRs are used in
special domains to point to some other location in the domain space.These
records are simple data, and dont imply any special processing similar to that
performed by CNAME, which identifies aliases. See the description of the INADDR.ARPA domain for an example.
3.3.13 SOA RDATA format
MNAME
The <domain-name> of the name server that was the original or primary source
of data for this zone.
RNAME
A <domain-name> which specifies the mailbox of the person responsible for this
zone.
SERIAL
The unsigned 32 bit version number of the original copy of the zone. Zone
transfers preserve this value. This value wraps and should be compared using
Hosts that have multiple Internet addresses will have multiple A records.
A records cause no additional section processing. The RDATA section of an A line
in a master file is an Internet address expressed as four decimal numbers
separated by dots without any imbedded spaces (e.g., 10.2.0.52 or 192.0.5.6).
3.4.2 WKS RDATA format
ADDRESS
A 32-bit Internet address
PROTOCOL
An 8-bit IP protocol number
<BIT MAP>
A variable length bit map. The bit map must be a multiple of 8 bits long.
The WKS record is used to describe the well known services supported by a
particular protocol on a particular internet address. The PROTOCOL field
specifies an IP protocol number, and the bit map has one bit per port of the
specified protocol. The first bit corresponds to port 0, the second to port 1, etc. If
the bit map does not include a bit for a protocol of interest, that bit is assumed
zero. The appropriate values and mnemonics for ports and protocols are specified
in [RFC-1010].
For example, if PROTOCOL=TCP (6), the 26th bit corresponds to TCP port 25
(SMTP). If this bit is set, a SMTP server should be listening on TCP port 25; if
zero, SMTP service is not supported on the specified address.
The purpose of WKS RRs is to provide availability information for servers for TCP
and UDP. If a server supports both TCP and UDP, or has multiple Internet
addresses, then multiple WKS RRs are used.
WKS RRs cause no additional section processing.
In master files, both ports and protocols are expressed using mnemonics or
decimal numbers.
by inverse queries; the difference is that this part of the domain name space is
structured according to address, and hence can guarantee that the appropriate
data can be located without an exhaustive search of the domain space.
The domain begins at IN-ADDR.ARPA and has a substructure which follows the
Internet addressing structure.
Domain names in the IN-ADDR.ARPA domain are defined to have up to four
labels in addition to the IN-ADDR.ARPA suffix. Each label represents one octet of
an Internet address, and is expressed as a character string for a decimal value in
the range 0-255 (with leading zeros omitted except in the case of a zero octet
which is represented by a single zero).
Host addresses are represented by domain names that have all four labels
specified. Thus data for Internet address 10.2.0.52 is located at domain name
52.0.2.10.IN-ADDR.ARPA. The reversal, though awkward to read, allows zones to
be delegated which are exactly one network of address space. For example,
10.IN-ADDR.ARPA can be a zone containing data for the ARPANET, while 26.INADDR.ARPA can be a separate zone for MILNET. Address nodes are used to
hold pointers to primary host names in the normal domain space.
Network numbers correspond to some non-terminal nodes at various depths in
the IN-ADDR.ARPA domain, since Internet network numbers are either 1, 2, or 3
octets. Network nodes are used to hold pointers to the primary host names of
gateways attached to that network. Since a gateway is, by definition, on more
than one network, it will typically have two or more network nodes which point at
it. Gateways will also have host level pointers at their fully qualified addresses.
Both the gateway pointers at network nodes and the normal host pointers at full
address nodes use the PTR RR to point back to the primary domain names of the
corresponding hosts.
For example, the IN-ADDR.ARPA domain will contain information about the ISI
gateway between net 10 and 26, an MIT gateway from net 10 to MITs net 18, and
hosts A.ISI.EDU and MULTICS.MIT.EDU. Assuming that ISI gateway has
addresses 10.2.0.22 and 26.0.0.103, and a name MILNET-GW.ISI.EDU, and the
MIT gateway has addresses 10.0.0.77 and 18.10.0.4 and a name
GW.LCS.MIT.EDU, the domain database would contain:
10.IN-ADDR.ARPA. PTR MILNET-GW.ISI.EDU.
10.IN-ADDR.ARPA. PTR GW.LCS.MIT.EDU.
18.IN-ADDR.ARPA. PTR GW.LCS.MIT.EDU.
26.IN-ADDR.ARPA. PTR MILNET-GW.ISI.EDU.
22.0.2.10.IN-ADDR.ARPA. PTR MILNET-GW.ISI.EDU.
103.0.0.26.IN-ADDR.ARPA. PTR MILNET-GW.ISI.EDU.
77.0.0.10.IN-ADDR.ARPA. PTR GW.LCS.MIT.EDU.
4.0.10.18.IN-ADDR.ARPA. PTR GW.LCS.MIT.EDU.
103.0.3.26.IN-ADDR.ARPA. PTR A.ISI.EDU.
6.0.0.10.IN-ADDR.ARPA. PTR MULTICS.MIT.EDU.
The program could then originate QTYPE=A, QCLASS=IN queries for MILNETGW.ISI.EDU. and GW.LCS.MIT.EDU. to discover the Internet addresses of these
gateways.
A resolver which wanted to find the host name corresponding to Internet host
address 10.0.0.6 would pursue a query of the form QTYPE=PTR, QCLASS=IN,
QNAME=6.0.0.10.IN-ADDR.ARPA, and would receive:
6.0.0.10.IN-ADDR.ARPA. PTR MULTICS.MIT.EDU.
4. Messages
4.1 Format
All communications inside of the domain protocol are carried in a single format
called a message. The top level format of message is divided into 5 sections
(some of which are empty in certain cases) shown below:
+---------------------+
| Header |
+---------------------+
| Question |
+---------------------+
| Answer |
+---------------------+
| Authority |
+---------------------+
| Additional |
+---------------------+
The header section is always present. The header includes fields that specify
which of the remaining sections are present, and also specify whether the
message is a query or a response, a standard query or some other opcode, etc.
The names of the sections after the header are derived from their use in standard
queries. The question section contains fields that describe a question to a name
server. These fields are a query type (QTYPE), a query class (QCLASS), and a
query domain name (QNAME). The last three sections have the same format: a
possibly empty list of concatenated resource records (RRs). The answer section
contains RRs that answer the question; the authority section contains RRs that
point toward an authoritative name server; the additional records section contains
RRs which relate to the query, but are not strictly answers for the question.
4.1.1 Header section format
The header contains the following fields:
1 1 1 1 1 1
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
| ID |
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|QR| Opcode |AA|TC|RD|RA| Z | RCODE |
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
| QDCOUNT |
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
| ANCOUNT |
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
| NSCOUNT |
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
| ARCOUNT |
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
where:
ID
A 16 bit identifier assigned by the program that generates any kind of query.
This identifier is copied the corresponding reply and can be used by the
requester to match up replies to outstanding queries.
QR
A one bit field that specifies whether this message is a query (0), or a response
(1).
OPCODE
A four bit field that specifies kind of query in this message. This value is set by
the originator of a query and copied into the response. The values are: 0 a
standard query (QUERY)1 an inverse query (IQUERY)2 a server status
request (STATUS)3-15 reserved for future use
AA
Authoritative Answer - this bit is valid in responses, and specifies that the
responding name server is an authority for the domain name in question
section. Note that the contents of the answer section may have multiple owner
names because of aliases. The AA bit corresponds to the name which matches
the query name, or the first owner name in the answer section.
TC
TrunCation - specifies that this message was truncated due to length greater
than that permitted on the transmission channel.
RD
Recursion Desired - this bit may be set in a query and is copied into the
response. If RD is set, it directs the name server to pursue the query
recursively. Recursive query support is optional.
RA
Recursion Available - this be is set or cleared in a response, and denotes
whether recursive query support is available in the name server.
Z
Reserved for future use. Must be zero in all queries and responses.
RCODE
Response code - this 4 bit field is set as part of responses. The values have the
following interpretation: 0 No error condition 1 Format error - The name
server was unable to interpret the query. 2 Server failure - The name server
was unable to process this query due to a problem with the name server. 3
Name Error - Meaningful only for responses from an authoritative name server,
this code signifies that the domain name referenced in the query does not exist.
4 Not Implemented - The name server does not support the requested kind of
where:
QNAME
a domain name represented as a sequence of labels, where each label consists
of a length octet followed by that number of octets. The domain name
terminates with the zero length octet for the null label of the root. Note that this
field may be an odd number of octets; no padding is used.
QTYPE
a two octet code which specifies the type of the query. The values for this field
include all codes valid for a TYPE field, together with some more general codes
which can match more than one type of RR.
QCLASS
a two octet code that specifies the class of the query. For example, the
QCLASS field is IN for the Internet.
4.1.3 Resource record format
The answer, authority, and additional sections all share the same format: a
variable number of resource records, where the number of records is specified in
the corresponding count field in the header. Each resource record has the
following format:
1 1 1 1 1 1
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
/ NAME /
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
| TYPE |
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
| CLASS |
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
| TTL |
| |
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
| RDLENGTH |
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--|
/ RDATA /
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
where:
NAME
a domain name to which this resource record pertains.
TYPE
two octets containing one of the RR type codes. This field specifies the
meaning of the data in the RDATA field.
CLASS
two octets which specify the class of the data in the RDATA field.
TTL
a 32 bit unsigned integer that specifies the time interval (in seconds) that the
resource record may be cached before it should be discarded. Zero values are
interpreted to mean that the RR can only be used for the transaction in
progress, and should not be cached.
RDLENGTH
an unsigned 16 bit integer that specifies the length in octets of the RDATA field.
RDATA
a variable length string of octets that describes the resource. The format of this
information varies according to the TYPE and CLASS of the resource record.
For example, the if the TYPE is A and the CLASS is IN, the RDATA field is a 4
The first two bits are ones. This allows a pointer to be distinguished from a label,
since the label must begin with two zero bits because labels are restricted to 63
octets or less. (The 10 and 01 combinations are reserved for future use.) The
OFFSET field specifies an offset from the start of the message (i.e., the first octet
of the ID field in the domain header). A zero offset specifies the first byte of the ID
field, etc.
The compression scheme allows a domain name in a message to be represented
as either:
a sequence of labels ending in a zero octet
a pointer
a sequence of labels ending with a pointer
Pointers can only be used for occurances of a domain name where the format is
not class specific. If this were not the case, a name server or resolver would be
required to know the format of all RRs it handled. As yet, there are no such cases,
but they may occur in future RDATA formats.
If a domain name is contained in a part of the message subject to a length field
(such as the RDATA section of an RR), and compression is used, the length of the
compressed name is used in the length calculation, rather than the length of the
expanded name.
Programs are free to avoid using pointers in messages they generate, although
this will reduce datagram capacity, and may cause truncation. However all
programs are required to understand arriving messages that contain pointers.
For example, a datagram might need to use the domain names F.ISI.ARPA,
FOO.F.ISI.ARPA, ARPA, and the root. Ignoring the other fields of the message,
these domain names might be represented as:
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
20 | 1 | F |
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
22 | 3 | I |
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
24 | S | I |
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
26 | 4 | A |
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
28 | R | P |
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
30 | A | 0 |
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
40 | 3 | F |
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
42 | O | O |
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
44 | 1 1| 20 |
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
64 | 1 1| 26 |
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
92 | 0 | |
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
The domain name for F.ISI.ARPA is shown at offset 20. The domain name
FOO.F.ISI.ARPA is shown at offset 40; this definition uses a pointer to
concatenate a label for FOO to the previously defined F.ISI.ARPA. The domain
name ARPA is defined at offset 64 using a pointer to the ARPA component of the
name F.ISI.ARPA at 20; note that this pointer relies on ARPA being the last label in
the string at 20. The root domain name is defined by a single octet of zeros at 92;
the root domain name has no labels.
4.2 Transport
The DNS assumes that messages will be transmitted as datagrams or in a byte
stream carried by a virtual circuit. While virtual circuits can be used for any DNS
activity, datagrams are preferred for queries due to their lower overhead and
better performance. Zone refresh activities must use virtual circuits because of the
need for reliable transfer.
The Internet supports name server access using TCP [RFC-793] on server port
53 (decimal) as well as datagram access using UDP [RFC-768] on UDP port 53
(decimal).
4.2.1 UDP usage
Messages sent using UDP user server port 53 (decimal).
Messages carried by UDP are restricted to 512 bytes (not counting the IP or UDP
headers). Longer messages are truncated and the TC bit is set in the header.
UDP is not acceptable for zone transfers, but is the recommended method for
standard queries in the Internet. Queries sent using UDP may be lost, and hence
a retransmission strategy is required. Queries or their responses may be
5. Master Files
Master files are text files that contain RRs in text form. Since the contents of a
zone can be expressed in the form of a list of RRs a master file is most often used
to define a zone, though it can be used to list a caches contents. Hence, this
section first discusses the format of RRs in a master file, and then the special
considerations when a master file is used to create a zone in some name server.
5.1 Format
The format of these files is a sequence of entries. Entries are predominantly lineoriented, though parentheses can be used to continue a list of items across a line
boundary, and text literals can contain CRLF within the text. Any combination of
tabs and spaces act as a delimiter between the separate items that make up an
entry. The end of any line in the master file can end with a comment. The
comment starts with a ; (semicolon).
The following entries are defined:
<blank>[<comment>]
$ORIGIN <domain-name> [<comment>]
$INCLUDE <file-name> [<domain-name>] [<comment>]
<domain-name><rr> [<comment>]
<blank><rr> [<comment>]
Blank lines, with or without comments, are allowed anywhere in the file.
Two control entries are defined: $ORIGIN and $INCLUDE. $ORIGIN is followed
by a domain name, and resets the current origin for relative domain names to the
stated name. $INCLUDE inserts the named file into the current file, and may
optionally specify a domain name that sets the relative domain name origin for the
included file. $INCLUDE may also have a comment. Note that a $INCLUDE entry
never changes the relative origin of the parent file, regardless of changes to the
relative origin made within the included file.
The last two forms represent RRs. If an entry for an RR begins with a blank, then
the RR is assumed to be owned by the last stated owner. If an RR entry begins
with a <domain-name>, then the owner name is reset.
<rr> contents take one of the following forms:
[<TTL>] [<class>] <type> <RDATA>
[<class>] [<TTL>] <type> <RDATA>
The RR begins with optional TTL and class fields, followed by a type and RDATA
field appropriate to the type and class. Class and type use the standard
mnemonics, TTL is a decimal integer. Omitted class and TTL values are default to
the last explicitly stated values. Since type and class mnemonics are disjoint, the
parse is unique. (Note that this order is different from the order used in examples
and the order used in the actual RRs; the given order allows easier parsing and
defaulting.)
<domain-name>s make up a large share of the data in the master file. The labels
in the domain name are expressed as character strings and separated by dots.
Quoting conventions allow arbitrary characters to be stored in domain names.
Domain names that end in a dot are called absolute, and are taken as complete.
Domain names which do not end in a dot are called relative; the actual domain
name is the concatenation of the relative part with an origin specified in a
$ORIGIN, $INCLUDE, or as an argument to the master file loading routine. A
relative name is an error when no origin is available.
<character-string> is expressed in one or two ways: as a contiguous set of
characters without interior spaces, or as a string beginning with a and ending
with a . Inside a delimited string any character can occur, except for a itself,
which must be quoted using \ (back slash).
Because these files are text files several special encodings are necessary to allow
arbitrary data to be loaded. In particular:
of the root.
@
A free standing @ is used to denote the current origin.
\X
where X is any character other than a digit (0-9), is used to quote that character
so that its special meaning does not apply. For example, . can be used to
place a dot character in a label.
\DDD
where each D is a digit is the octet corresponding to the decimal number
described by DDD. The resulting octet is assumed to be text and is not checked
for special meaning.
( )
Parentheses are used to group data that crosses a line boundary. In effect, line
terminations are not recognized within parentheses.
;
Semicolon is used to start a comment; the remainder of the line is ignored.
When a master file is used to load a zone, the operation should be suppressed if
any errors are encountered in the master file. The rationale for this is that a single
error can have widespread consequences. For example, suppose that the RRs
defining a delegation have syntax errors; then the server will return authoritative
name errors for all names in the subzone (except in the case where the subzone
is also present on the server).
Several other validity checks that should be performed in addition to insuring that
the file is syntactically correct:
1. All RRs in the file should have the same class.
2. Exactly one SOA RR should be present at the top of the zone.
3. If delegations are present and glue information is required, it should be
present.
4. Information present outside of the authoritative nodes in the zone should be
glue information, rather than the result of an origin or similar error.
more), but the vast majority have a very low branching factor (0-1).
One way to solve the case problem is to store the labels for each node in two
pieces: a standardized-case representation of the label where all ASCII
characters are in a single case, together with a bit mask that denotes which
characters are actually of a different case. The branching factor diversity can be
handled using a simple linked list for a node until the branching factor exceeds
some threshold, and transitioning to a hash structure after the threshold is
exceeded. In any case, hash structures used to store tree sections must insure
that hash functions and procedures preserve the casing conventions of the DNS.
The use of separate structures for the different parts of the database is motivated
by several factors:
The catalog structure can be an almost static structure that need change only
when the system administrator changes the zones supported by the server.
This structure can also be used to store parameters used to control
refreshing activities.
The individual data structures for zones allow a zone to be replaced simply by
changing a pointer in the catalog. Zone refresh operations can build a new
structure and, when complete, splice it into the database via a simple pointer
replacement. It is very important that when a zone is refreshed, queries
should not use old and new data simultaneously.
With the proper search procedures, authoritative data in zones will always
hide, and hence take precedence over, cached data.
Errors in zone definitions that cause overlapping zones, etc., may cause
erroneous responses to queries, but problem determination is simplified, and
the contents of one bad zone cant corrupt another.
Since the cache is most frequently updated, it is most vulnerable to corruption
during system restarts. It can also become full of expired RR data. In either
case, it can easily be discarded without disturbing zone data.
A major aspect of database design is selecting a structure which allows the name
server to deal with crashes of the name servers host. State information which a
name server should save across system crashes includes the catalog structure
(including the state of refreshing for each zone) and the zone data itself.
6.1.3 Time
Both the TTL data for RRs and the timing data for refreshing activities depends on
32 bit timers in units of seconds. Inside the database, refresh timers and TTLs for
cached data conceptually count down, while data in the zone stays with constant
TTLs.
Inverse queries take the form of a single RR in the answer section of the
message, with an empty question section. The owner name of the query RR and
its TTL are not significant. The response carries questions in the question section
which identify all names possessing the query RR WHICH THE NAME SERVER
KNOWS. Since no name server knows about all of the domain name space, the
response can never be assumed to be complete. Thus inverse queries are
primarily useful for database management and debugging activities. Inverse
queries are NOT an acceptable method of mapping host addresses to host
names; use the IN-ADDR.ARPA domain instead.
Where possible, name servers should provide case-insensitive comparisons for
inverse queries. However, this cannot be guaranteed because name servers may
possess RRs that contain character strings but the name server does not know
that the data is character.
When a name server processes an inverse query, it either returns:
1. zero, one, or multiple domain names for the specified resource as QNAMEs
in the question section
2. an error code indicating that the name server doesnt support inverse
mapping of the specified resource type.
When the response to an inverse query contains one or more QNAMEs, the
owner name and TTL of the RR in the answer section which defines the inverse
query is modified to exactly match an RR found at the first QNAME.
RRs returned in the inverse queries cannot be cached using the same mechanism
as is used for the replies to standard queries. One reason for this is that a name
might have multiple RRs of the same type, and only one would appear.
6.4.2 Inverse query and response example
The overall structure of an inverse query for retrieving the domain name that
corresponds to Internet address 10.1.0.52 is shown below:
+-----------------------------------------+
Header | OPCODE=IQUERY, ID=997 |
+-----------------------------------------+
Question | <empty> |
+-----------------------------------------+
Answer | <anyname> A IN 10.1.0.52 |
+-----------------------------------------+
Authority | <empty> |
+-----------------------------------------+
Additional | <empty> |
+-----------------------------------------+
This query asks for a question whose answer is the Internet style address
10.1.0.52. Since the owner name is not known, any domain name can be used as
a placeholder (and is ignored). A single octet of zero, signifying the root, is usually
used because it minimizes the length of the message. The TTL of the RR is not
significant. The response to this query might be:
+-----------------------------------------+
Header | OPCODE=RESPONSE, ID=997 |
+-----------------------------------------+
Question |QTYPE=A, QCLASS=IN, QNAME=VENERA.ISI.EDU |
+-----------------------------------------+
Answer | VENERA.ISI.EDU A IN 10.1.0.52 |
+-----------------------------------------+
Authority | <empty> |
+-----------------------------------------+
Additional | <empty> |
+-----------------------------------------+
Note that the QTYPE in a response to an inverse query is the same as the TYPE
field in the answer section of the inverse query. Responses to inverse queries
may contain multiple questions when the inverse is not unique. If the question
section in the response is not empty, then the RR in the answer section is
modified to correspond to be an exact copy of an RR at the first QNAME.