HTTP (HyperText Transfer Protocol - протокол передачи гипертекста)честно следует своему названию, и заключается в спецификации обмена сообщениями определенного текстового формата. Клиент и сервер обмениваются текстовыми сообщениями состоящими из заголовка сообщения и его тела. В заголовке указывается необходимая для взаимодействия информация.
Http заголовок запроса
Чтобы обратиться к серверу, клиент должен послать сообщение, которое в простейшем случае будет выглядеть следующим образом:GET HTTP/1.1
И так, у нас есть текстовое сообщение, которое необходимо передать программе, возможно запущенной на совершенно иной, удаленной машине. Благодаря протоколу TCP/IP эта задача кажется элементарной, для ее реализации осталось лишь узнать адрес целевого сервера. Для этого в протоколе http существует специальное поле: Host
GET HTTP/1.1 Host: www.w3.org:80
На самом деле, спецификация http предусматривает достаточно много полей заголовка сообщения отправляемого клиентом (чаще всего браузером). Заголовок редко ограничивается парой строк и чаще выглядит примерно так:
GET http://www.w3.org/standards/webdesign/htmlcss HTTP/1.1 Host: www.w3.org Accept: image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, */* Referer: http://localhost/ Accept-Language: ru User-Agent: Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.0; .NET CLR 1.0.3705) Proxy-Connection: Keep-Alive
Кратко рассмотрим самые популярные поля заголовка.
Строка запроса (Request-Line)
Все начинается с первой, обязательной строки запроса (Request-Line). Она должна содержать название команды/метода (Method), полный путь запрашиваемого ресурса(Request-URI), который может и отсутствовать, и версию протокола, поддерживаемую клиентом:
Request-Line = Method SP Request-URI SP HTTP-Version CRLF
Метод указывает действие, которое должен выполнить сервер с указанным ресурсом. В протоколе определены следующие методы:- OPTIONS - Используется для определения возможностей веб-сервера или параметров соединения для конкретного ресурса.
- GET - Используется для запроса содержимого указанного ресурса. С помощью метода GET можно также начать какой-либо процесс.
- HEAD - Аналогичен методу GET, за исключением того, что в ответе сервера отсутствует тело. Может использоваться для проверки доступности сервера.
- POST - Применяется для передачи пользовательских данных заданному ресурсу. При этом передаваемые данные включаются в тело запроса. Так же, с помощью этого метода можно загружать файлы на сервер.
- PUT - Применяется для загрузки содержимого запроса на указанный в запросе адрес. Если по заданному адресу не существовало ресурса, то сервер создаёт его и возвращает статус 201 (Created). Если же был изменён ресурс, то сервер возвращает 200 (Ok) или 204 (No Content).
- DELETE - Команда на удаление указанного ресурса. Если это действие запрещено, возвращается код 403 (Forbidden).
- TRACE - Возвращает полученный запрос так, что клиент может увидеть, какую информацию промежуточные серверы добавляют или изменяют в запросе.
- CONNECT - Преобразует соединение запроса в прозрачный TCP/IP туннель, обычно чтобы содействовать установлению защищенного SSL соединения через не шифрованный прокси.
Адрес сервера (Host)
Host: www.w3.org
Поддерживаемые типы файлов (Accept)
Содержит перечень MIME типов файлов, которые может обработать клиент:Accept: image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, */*
Адрес перехода(Referer)
Указывает адрес, с которого был выполнен переход.Referer: http://localhost/
Поддерживаемый язык (Accept-Language)
Accept-Language: ru
Пользовательский клиент (User-Agent)
User-Agent: Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.0; MyIE2; .NET CLR 1.0.3705)
"Печеньки" (Cookie)
Cookie: param1=value1; param2=value2
Диапазон(Range)
Range-Unit: 2048 | 1024
Эта статья не претендует на исчерпывающее изложение деталей http протокола. Наиболее подробную информацию о нем вы можете найти по адресу: RFC 2616.
Простейший Http клиент на Java
Пришло время все "потрогать собственными руками". Для этого я предлагаю простейшую реализацию http клиента, который, следуя unix-way (о нем в ближайшей статье), будет получать на вход содержимое заголовка сообщения и отправлять его на сервер.
import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.Socket;
import java.text.ParseException;
public class HttpClient {
public static void main(String[] args) {
try {
String header = null;
if (args.length == 0) {
header = readHeader(System.in);
} else {
FileInputStream fis = new FileInputStream(args[0]);
header = readHeader(fis);
fis.close();
}
System.out.println("Заголовок: \n" + header);
/* Запрос отправляется на сервер */
String answer = sendRequest(header);
/* Ответ выводится на консоль */
System.out.println("Ответ от сервера: \n");
System.out.write(answer.getBytes());
} catch (Exception e) {
System.err.println(e.getMessage());
e.getCause().printStackTrace();
}
}
/**
* Читает поток и возвращает его содержимое в виде строки.
*/
public static String readHeader(InputStream strm) throws IOException {
byte[] buff = new byte[64 * 1024];
int length = strm.read(buff);
String res = new String(buff, 0, length);
return res;
}
/**
* Отправляет запрос в соответствии с Http заголовком.
*
* @return ответ от сервера.
*/
public static String sendRequest(String httpHeader) throws Exception {
/* Из http заголовка берется арес сервера */
String host = null;
int port = 0;
try {
host = getHost(httpHeader);
port = getPort(host);
host = getHostWithoutPort(host);
} catch (Exception e) {
throw new Exception("Не удалось получить адрес сервера.", e);
}
/* Отправляется запрос на сервер */
Socket socket = null;
try {
socket = new Socket(host, port);
System.out.println("Создан сокет: " + host + " port:" + port);
socket.getOutputStream().write(httpHeader.getBytes());
System.out.println("Заголовок отправлен. \n");
} catch (Exception e) {
throw new Exception("Ошибка при отправке запроса: "
+ e.getMessage(), e);
}
/* Ответ от сервера записывается в результирующую строку */
String res = null;
try {
InputStreamReader isr = new InputStreamReader(socket
.getInputStream());
BufferedReader bfr = new BufferedReader(isr);
StringBuffer sbf = new StringBuffer();
int ch = bfr.read();
while (ch != -1) {
sbf.append((char) ch);
ch = bfr.read();
}
res = sbf.toString();
} catch (Exception e) {
throw new Exception("Ошибка при чтении ответа от сервера.", e);
}
socket.close();
return res;
}
/**
* Возвращает имя хоста (при наличии порта, с портом) из http заголовка.
*/
private static String getHost(String header) throws ParseException {
final String host = "Host: ";
final String normalEnd = "\n";
final String msEnd = "\r\n";
int s = header.indexOf(host, 0);
if (s < 0) {
return "localhost";
}
s += host.length();
int e = header.indexOf(normalEnd, s);
e = (e > 0) ? e : header.indexOf(msEnd, s);
if (e < 0) {
throw new ParseException(
"В заголовке запроса не найдено " +
"закрывающих символов после пункта Host.",
0);
}
String res = header.substring(s, e).trim();
return res;
}
/**
* Возвращает номер порта.
*/
private static int getPort(String hostWithPort) {
int port = hostWithPort.indexOf(":", 0);
port = (port < 0) ? 80 : Integer.parseInt(hostWithPort
.substring(port + 1));
return port;
}
/**
* Возвращает имя хоста без порта.
*/
private static String getHostWithoutPort(String hostWithPort) {
int portPosition = hostWithPort.indexOf(":", 0);
if (portPosition < 0) {
return hostWithPort;
} else {
return hostWithPort.substring(0, portPosition);
}
}
}
$> javac HttpClient.java
Создаем текстовый файл Header.txt и сохраняем в нем пример многострочного заголовка приведенного выше, заменив метод Get на Head. Напоминаю, в результате метода Get сервер вернет полное сообщение - и заголовок и тело. Но сейчас для нас тело не представляет особенного интереса т.к. представляет собой Html разметку страницы. Гораздо интереснее взглянуть на заголовок ответа. Именно для этого и существует метод Head. Он указывает серверу, что надо вернуть только заголовок сообщения и опустить его тело.HEAD http://www.w3.org/standards/webdesign/htmlcss HTTP/1.1 Host: www.w3.org Accept: image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, */* Referer: http://localhost/ Accept-Language: ru User-Agent: Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.0; .NET CLR 1.0.3705) Proxy-Connection: Keep-Alive
$> cat Header.txt | java HttpClient
*в Windows аналогом команды cat является команда typeHttp заголовок ответа
На момент статьи ответ сервера был следующим:HTTP/1.0 200 OK Date: Fri, 24 Feb 2012 06:56:41 GMT Server: Apache/2 Content-Location: htmlcss.html Vary: negotiate TCN: choice Last-Modified: Fri, 24 Feb 2012 03:02:02 GMT ETag: "6063-4b9acfc1b3e80;4a8c5934a8200" Accept-Ranges: bytes Content-Length: 24675 Cache-Control: max-age=21600 Expires: Fri, 24 Feb 2012 12:56:41 GMT P3P: policyref="http://www.w3.org/2001/05/P3P/p3p.xml" Content-Type: text/html; charset=utf-8
Список полей заголовка ответного сообщения велик и целиком его можно по прежнему увидеть здесь. Но для общего развития разберем пару параметров, что встретились в нашем примере.
И снова обязательной является только первая строка (строка состояния). В ней указывается версия протокола, трехзначный код ответа и его расшифровка:
Status-Line = HTTP-Version SP Status-Code SP Reason-Phrase CRLF
- 1xx: Informational(Информационные) - Запрос получен. Продолжается процесс.
- 2xx: Success(Успешные) - Запрос был успешно получен, распознан и принят на обработку.
- 3xx: Redirection(Коды перенаправления) - Необходимы дополнительные действия для выполнения запроса. Чаще всего, речь идет о перенаправлении на ресурс, указанный в поле Location.
- 4xx: Client Error(Коды ошибок клиента) - Запрос не может быть выполнен. Чаще всего по причине некорректного синтаксиса.
- 5xx: Server Error(Коды ошибок сервера) - Сервер не в состоянии правильно выполнить запрос. Такие ошибки часто связаны с ограничениями прав.
- "100" Continue(Продолжение) - Сервер оповещает клиента о том, что часть его сообщения принята и ему следует продолжать запрос.
- "200" OK(ОК он и Африке ОК) - Запрос выполнен успешно. Информация в ответе зависит от типа запроса POST, GET или HEAD.
- "201" Created(Создано) - Запрос выполнен успешно. Ресурс создан. Местоположение созданного ресурса указывается в поле Location.
- "202" Accepted (Принято) - Запрос был принят на обработку, но обработка еще не завершена. Сервер оповещает клиента о том, что ему не обязательно дожидаться окончательной передачи сообщения, так как процесс обработки может затянуться надолго. Типовой случай - передача файла.
- "300" Multiple Choices(Множественный выбор) - Говорит о том, что по указанному URI сервер может предоставить несколько вариантов ресурса в зависимости от языка, MIME типа или другому признаку. Выбор предлагается сделать клиенту.
- "400" Bad Request(Плохой запрос) - Признак синтаксической ошибки в запросе. (Чтобы воспроизвести,отправьте запрос, содержащий русские буквы, например напишите метод в транслите).
- "404" Not Found(Не найдено) - Легендарная ошибка, с которой сталкивался каждый. Возникает по причине того, что сервер понял запрос, но не смог найти указанный ресурс. Чаще всего возникает по причине опечаток в URL.
- "405" Method Not Allowed(Метод не поддерживается) - Говорит о недопустимости запрашиваемого действия к ресурсу. (Чтобы воспроизвести, попробуйте отправить запрос DELETE к, практически любому ресурсу).
- "418" I'm a teapot (Я - чайник) - Возвращается при попытке заварить кофе в заварном чайнике. Серверу следует вернуть короткий и жёсткий ответ. (Часть протокола гипертекстового контроля кофеварками).
- "500" Internal Server Error(Внутренняя ошибка сервера) - Сервер столкнулся с непредвиденными проблемами, не позволяющими ему выполнить запрос.
- "501" Not Implemented(Не реализовано) - Сервер не поддерживает возможностей указанных в запросе. (Редкие серверы могут варить кофе. Если в методе указать BREW, то скорее всего именно этот ответ вы и получите).
- "503" Service Unavailable(Сервер недоступен) - Сервер временно не в состоянии обрабатывать запросы.
- "505" HTTP Version not supported(Указанная версия протокола Http не поддерживается) - название говорит само за себя.
Date: Fri, 24 Feb 2012 06:56:41 GMT
Затем указывается сервер
Server: Apache/2
Accept-Ranges: bytes
Следующий параметр указывает длину отправленного сервером документа
Content-Length: 24675
Content-Type: text/html; charset=utf-8
Подытожим
Эта статья не раскрыла новых тонкостей Java API для работы с http, но должна была привнести ясность в понимание того, чем же на самом деле является http: это не более чем договоренность о формате сообщений пересылаемых между сервером и клиентом. Способ их доставки играет отнюдь не ключевую роль. Вместо TCP/IP с задачей реализации http может справиться и Почта России, и почтовые голуби. Было бы желание! =)UPD: теперь доступен репозиторий, для цикла статей об http в java: https://bitbucket.org/dok/http
2 комментария:
Спасибо за статью!
Я впервые сталкиваюсь с HTTP и мне помогла эта статья.
Вообще интересовали только GET и POST,гуглил на других сайтах и не увидел ни одного прозрачного ответа.
Автору респект =)
Отправить комментарий