1 В избранное 0 Ответвления 0

OSCHINA-MIRROR/wizardforcel-thinking-in-java-zh

Присоединиться к Gitlife
Откройте для себя и примите участие в публичных проектах с открытым исходным кодом с участием более 10 миллионов разработчиков. Приватные репозитории также полностью бесплатны :)
Присоединиться бесплатно
В этом репозитории не указан файл с открытой лицензией (LICENSE). При использовании обратитесь к конкретному описанию проекта и его зависимостям в коде.
Клонировать/Скачать
15.6 Java与CGI的沟通.md 66 КБ
Копировать Редактировать Web IDE Исходные данные Просмотреть построчно История
Отправлено 11.03.2025 09:15 d56454c

15.6 Связь Java с CGI

Java-программа может отправить запрос CGI-серверу, что ничем не отличается от HTML-формы. Как и в случае с HTML-страницей, этот запрос можно настроить как GET (скачивание) или POST (загрузка). Кроме того, Java-программа может перехватывать вывод CGI-программы, поэтому нет необходимости полагаться на программу для форматирования новой страницы, а также нет необходимости заставлять пользователя переходить между страницами при возникновении ошибок. В действительности, внешний вид программы может быть сделан таким же, как и в предыдущих версиях.

Код также будет проще, поскольку использование CGI не представляет особого труда (при условии правильного понимания его работы). Поэтому в этой главе мы проведём краткий курс по программированию CGI. Для решения типовых задач будут созданы некоторые CGI-инструменты на C++, чтобы мы могли создать универсальную CGI-программу, решающую все проблемы. Преимуществом такого подхода является высокая способность к портированию — примеры, которые вы увидите, смогут работать на любой системе, поддерживающей CGI, и не столкнутся с проблемами сетевых экранов.

Этот пример также демонстрирует, как устанавливать соединение между апплетом и CGI-программой, чтобы легко адаптировать это решение к вашим проектам.## 15.6.1 Кодировка данных CGI

В этом варианте мы будем собирать имя и адрес электронной почты и сохранять их в следующем формате:

Имя Фамилия <email@domain.com>;

Это очень удобный формат для любой программы электронной почты. Поскольку требуется всего два поля, и CGI использует специальную кодировку для этих полей, здесь нет простых решений. Если вы создадите простую HTML-страницу и добавите следующие строки кода, вы сможете правильно понять эту концепцию:

<form method="GET" ACTION="/cgi-bin/Listmgr2.exe">
<p>Имя: <input TYPE="text" NAME="имя"
VALUE="" size="40"></p>
<p>Адрес электронной почты: <input TYPE="text"
NAME="электронная почта" VALUE="" size="40"></p>
<p><input TYPE="submit" NAME="submit"> </p>
</form>
```Вышеуказанный код создаёт два поля ввода данных, названных `имя` и `электронная почта`. Также есть кнопка `submit` (отправить), которая собирает данные и передаёт их CGI-программе. `Listmgr2.exe` — это исполняемый файл, находящийся в специальной директории программ. На нашем веб-сервере эта директория обычно называется `cgi-bin` (Примечание ③). Если программа не найдена в указанной директории, то результат не будет получен. Заполнив эту форму и нажав кнопку отправки, вы можете увидеть следующее содержимое в окне URL вашего браузера:

### В Windows 32-битной среде можно использовать Microsoft Personal Web Server (личный веб-сервер от Microsoft), который поставляется вместе с Microsoft Office 97 или другими продуктами, чтобы провести тестирование. Это лучший способ проведения экспериментов, так как нет необходимости подключаться к сети, а тестирование можно выполнить локально (скорость также очень высока). Если используется другая платформа или нет продуктов, таких как Microsoft Office 97 или FrontPage 98, то можно найти бесплатный веб-сервер для тестирования онлайн.Конечно, указанный выше URL при отображении не будет переноситься на новую строку. Из этого можно немного понять, как кодируются данные и передаются через CGI. Минимум одно можно сказать точно — пробелы недопустимы (потому что они обычно используются для разделения параметров командной строки). Все необходимые пробелы заменены символом `+`, каждое поле имени следует за названием поля (которое определяется HTML-страницей), за которым следует знак `=`, а затем фактические данные поля, завершающиеся символом `&`.

На этом этапе вы можете задаться вопросом о использовании символов `+`, `=`, и `&`. Как объявить эти символы, если они должны использоваться внутри поля? Например, мы можем использовать имя "John & Marsha Smith", где `&` означает "And". На самом деле, это будет закодировано следующим образом:

John+%26+Marsha+Smith


То есть специальные символы преобразуются в `%`, за которым следует шестнадцатеричное ASCII-кодовое значение символа.

К счастью, Java предоставляет инструмент для помощи в этой задаче. Это статический метод класса `URLEncoder`, называемый `encode()`. Вы можете протестировать этот метод следующим образом:

//: EncodeDemo.java // Демонстрация метода URLEncoder.encode() import java.net.*;

public class EncodeDemo { public static void main(String[] args) { String s = ""; for(int i = 0; i < args.length; i++) s += args[i] + " "; s = URLEncoder.encode(s.trim()); System.out.println(s); } } ///:~ ```## Класс NameSender2, расширяющий `Applet`

Этот программный код получает несколько параметров командной строки, объединяет их в одну строку, состоящую из нескольких слов, разделенных пробелами (последний пробел удаляется с помощью String.trim()), после чего происходит кодировка и вывод результата. Для вызова CGI-программы все, что требуется от скрипта, — это сбор данных из своих полей или других мест, закодировать все данные в правильном формате URL, а затем объединить всё в один строковый объект. Каждое имя поля следует за которым следует знак =, затем сама информация, а после него — символ &. Для создания полной команды CGI этот строковый объект помещается после URL CGI-программы и вопросительного знака ?. Это стандартный метод вызова всех CGI-программ. Вы сразу заметите, как легко можно выполнить все эти действия с использованием одного скрипта.

15.6.2 Программный фрагмент

Программный фрагмент на самом деле проще, чем NameSender.java. В этом случае отправка запроса GET является простой задачей. Кроме того, нет необходимости ждать ответа. Теперь есть два поля вместо одного, но многие программные фрагменты будут знакомыми. Сравните с NameSender.java.

//: NameSender2.java
// Applet, который отправляет адрес электронной почты
// через CGI GET, используя Java 1.02.
import java.awt.*;
import java.applet.*;
import java.net.*;
import java.io.*;
```### Описание

Класс `NameSender2` является расширением класса `Applet`. Он включает в себя кнопку отправки, поля ввода имени и электронной почты, а также метки для отображения сообщений.

### Переменные

- **CGIProgram**: путь к исполняемому файлу CGI-программы (`"Listmgr2.exe"`).
- **send**: кнопка для отправки данных (`"Добавьте адрес электронной почты в рассылку"`).
- **name**: поле ввода имени пользователя (`"Введите ваше имя здесь"`, размер поля  40 символов).
- **email**: поле ввода адреса электронной почты (`"Введите адрес электронной почты здесь"`, размер поля  40 символов).
- **str**: строковое значение для хранения данных.
- **l**, **l2**: метки для отображения сообщений.

### Методы

#### Метод `init`

Инициализирует компоненты апплета:

```java
setLayout(new BorderLayout());

Panel p = new Panel();
p.setLayout(new GridLayout(3, 1));
p.add(name);
p.add(email);
p.add(send);

add("North", p);

Panel labels = new Panel();
labels.setLayout(new GridLayout(2, 1));
labels.add(l);
labels.add(l2);

add("Center", labels);

l.setText("Готово к отправке адреса электронной почты");

Метод action

Обрабатывает события, связанные с нажатием кнопки отправки:

if (evt.target.equals(send)) {
    l2.setText("");

    // Проверка ошибок в данных:
    if (name.getText().trim().indexOf(' ') == -1) {
        l.setText("Пожалуйста, укажите имя и фамилию");
        l2.setText("");
        return true;
    }

    str = email.getText().trim();

    if (str.indexOf(' ') != -1) {
        l.setText("Пробелы недопустимы в адресе электронной почты");
        l2.setText("");
        return true;
    }
``````markdown
    if (str.indexOf(',') != -1) {
        l.setText("Запятые недопустимы в адресе электронной почты");
        return true;
    }

    if (str.indexOf('@') == -1) {
        l.setText("Адрес электронной почты должен содержать '@'");
        l2.setText("");
        return true;
    }

    if (str.indexOf('@') == 0) {
        l.setText("Имя должно предшествовать '@' в адресе электронной почты");
        l2.setText("");
        return true;
    }

    String end = str.substring(str.indexOf('@'));

    if (end.indexOf('.') == -1) {
        l.setText("Часть после '@' должна иметь расширение, например '.com'");
        l2.setText("");
        return true;
    }

    // Создание и закодировка данных электронной почты:
    String emailData = "name=" + URLEncoder.encode(name.getText().trim())
                       + "&email=" + URLEncoder.encode(email.getText().trim().toLowerCase())
                       + "&submit=Submit";

    // Отправка имени с помощью процесса GET CGI:
    try {
        l.setText("Отправка...");
        URL u = new URL(getDocumentBase(), "cgi-bin/");

        CGIProgram + "? " + emailData);
        l.setText("Отправлено: " + email.getText());
        send.setLabel("Переслать");
        l2.setText(
            "Ожидание ответа " + ++vcount);
        DataInputStream server =
                new DataInputStream(u.openStream());
        String line;
        while ((line = server.readLine()) != null)
            l2.setText(line);
    } catch (MalformedURLException e) {
        l.setText("Недействительный URL");
    } catch (IOException e) {
        l.setText("Ошибка ввода-вывода");
    }
}
else return super.action(evt, arg);
return true;
}
///:~
```Пример перевода текстовых описаний, сообщений об ошибках и других элементов в соответствии с указанными правилами. Программа CGI (которую вы вскоре увидите) называется `Listmgr2.exe`. Многие веб-серверы работают на машинах с операционной системой Unix (Linux становится всё более популярным). По традиции они обычно не используют расширение `.exe` для своих исполняемых программ. Однако на Unix-системах можно назвать свои программы любым образом, как вам будет удобнее. Если используется расширение `.exe`, программа может проходить тестирование на Unix и Win32 без каких-либо изменений.Как обычно, фрагмент программы создаёт своё пользовательское окружение (в этот раз это два поля ввода вместо одного). Единственным значительным отличием является то, что оно возникает внутри метода `action()`. Этот метод отвечает за управление событиями нажатия кнопок. После проверки имени, вы заметите следующий ряд команд:

```java
String emailData =
  "name=" + URLEncoder.encode(
    name.getText().trim()) +
  "&email=" + URLEncoder.encode(
    email.getText().trim().toLowerCase()) +
  "&submit=Submit";
// Отправка имени через процесс GET CGI:
try {
  l.setText("Отправляется...");
  URL u = new URL(
    getDocumentBase(), "cgi-bin/" +
    CGIProgram + "?" + emailData);
  l.setText("Отправлено: " + email.getText());
  send.setLabel("Перезапросить");
  l2.setText(
    "Ожидание ответа " + ++vcount);
  DataInputStream server =
    new DataInputStream(u.openStream());
  String line;
  while ((line = server.readLine()) != null)
    l2.setText(line);
  // ...
}
```Данные `name` и `email` извлекаются из соответствующих полей ввода, а лишние пробелы удаляются с помощью метода `trim()`. Чтобы имя электронной почты могло быть добавлено в список, оно принудительно преобразуется в нижний регистр, чтобы обеспечить точное сравнение (чтобы избежать ошибок, основанных на различии регистра букв). Данные каждого поля закодированы в виде URL, после чего собраны в строку запроса GET таким же образом, как это делается в HTML-страницах (что позволяет объединять Java-фрагменты с любыми существующими программами CGI для выполнения обычных запросов GET HTML). До этого момента некоторые магические свойства Java начали проявляться: чтобы установить соединение с любым URL, достаточно создать объект типа URL и передать адрес конструктору. Конструктор берёт на себя задачу установления соединения с сервером (для веб-сервера все действия выполняются на основе строки, используемой как URL). В данном случае URL указывает на каталог `cgi-bin` текущего веб-сайта (основной адрес сайта задается методом `getDocumentBase()`). Как только веб-сервер видит `cgi-bin` в URL, он ожидает увидеть имя программы, находящейся в этом каталоге, которую нам нужно запустить. После имени программы следует вопросительный знак и строка параметров, которую CGI-программа будет искать в переменной окружения `QUERY_STRING` (мы скоро это узнаем).При отправке любого запроса мы обычно получаем ответ в виде HTML-страницы. Однако, используя объект URL в Java, можно перехватывать любую информацию, возвращаемую CGI-программой, просто получив `InputStream` (поток входных данных) из этого объекта URL. Это достигается с помощью метода `openStream()` объекта URL, который затем оборачивается в объект `DataInputStream`. После этого данные могут быть прочитаны построчно; если `readLine()` возвращает null (пустое значение), это указывает на то, что CGI-программа завершила свое выполнение.CGI-программы, которые мы будем рассматривать далее, будут возвращать всего одну строку, которая будет использоваться как метка успешности или неудачи (вместе с конкретной причиной ошибки). Эта строка будет захвачена и помещена во второе поле `Label`, чтобы пользователь мог видеть, что произошло.

(1) Отображение веб-страницы через программу

Программа также может отобразить результат работы CGI-программы как веб-страницу, аналогично тому, как это происходит при обычном режиме HTML. Для этого используется следующий код:

```java
getAppletContext().showDocument(u);

где u представляет собой объект URL. Этот пример демонстрирует простое переадресование на другой веб-сайт. Этот сайт случайно является выводом CGI-программы, но он может легко быть обычной HTML-страницей, поэтому можно создать апплет, который генерирует защищённый паролем шлюз, через который можно получить доступ к специальной части своего веб-сайта:

//: ShowHTML.java
import java.awt.*;
import java.applet.*;
import java.net.*;
import java.io.*;
``````java
public class ShowHTML extends Applet {
  static final String CGIProgram = "MyCGIProgram";
  Button send = new Button("Перейти");
  Label l = new Label();
  public void init() {
    add(send);
    add(l);
  }
  public boolean action(Event evt, Object arg) {
    if(evt.target.equals(send)) {
      try {
        // Это может быть обычная HTML-страница вместо
        // CGI-программы. Обратите внимание, что эта CGI-
        // программа не использует аргументы, но вы можете
        // добавить их обычным образом.
        URL u = new URL(getDocumentBase(), "cgi-bin/" + CGIProgram);
        // Отображение выходных данных URL с помощью
        // веб-браузера, как обычной страницы:
        getAppletContext().showDocument(u);
      } catch(Exception e) {
        l.setText(e.toString());
      }
    } else return super.action(evt, arg);
    return true;
  }
} ///:~
```Одним из самых больших преимуществ класса `URL` является его способность эффективно обеспечивать безопасность. Он позволяет установить соединение с веб-сервером без необходимости знать какие-либо детали, скрывающиеся за ним.

## 15.6.3 CGI программа, написанная на C++

После предыдущих занятий вы должны были научиться писать CGI программы для своего сервера с использованием ANSI C. Был выбран ANSI C потому что он широко распространен и является наиболее популярным стандартом языка C. Конечно же, сегодня C++ также очень популярен, особенно в форме компилятора GNU C++ (g++) (примечание ). g++ можно бесплатно скачать со многих сайтов в интернете, и доступна его версия практически для всех платформ (обычно она предоставляется вместе с операционной системой типа Linux и уже установлена заранее). Как вы вскоре увидите, от CGI программы можно получить множество преимуществ, связанных с объектно-ориентированным программированием.

: Полное название GNU  "Gnu's Not Unix". Это проект, который был первоначально разработан организацией Free Software Foundation (FSF) с целью замены существующего операционной системы Unix бесплатной версией. Сегодня Linux кажется тем, кто смог сделать то, что раньше было невозможно. Однако инструменты GNU сыграли ключевую роль в разработке Linux. В действительности, целый набор приложений Linux включает большое количество компонентов GNU.Чтобы избежать предложения слишком большого количества новых концепций сразу, эта программа не была создана как "чистая" C++ программа; некоторые части кода написаны на обычном C  хотя бы часть кода могла быть написана с использованием альтернативных форм C++. Но это не представляет собой особой проблемы, так как самое главное преимущество использования C++ заключается в возможности создания классов. При анализе CGI информации мы больше всего заботимся о парах "название/значение", поэтому используем класс (`Pair`) для представления каждого такого пары; другой класс (`CGI_vector`) автоматически распарсивает CGI строки в `vector`, содержащий объекты Pair (как вектор), таким образом позволяя нам извлекать каждую пару по мере необходимости.Эта программа также интересна тем, что демонстрирует многие преимущества и недостатки C++, сравнивая его с Java. Вы увидите некоторые похожие вещи; например, ключевое слово `class`. Контроль доступа осуществляется с помощью абсолютно таких же ключевых слов `public` и `private`, но они используются немного по-разному. Они контролируют блоки кода, а не отдельные методы или поля (то есть, если указано `private:`, каждый последующий определенный элемент будет иметь свойство `private`, до тех пор пока снова не будет указано `public:`). Кроме того, все определения внутри класса автоматически имеют значение `private`. Одним из причин использования C++ здесь заключается в возможности использовать удобства, предоставляемые "стандартной библиотекой шаблонов" (STL). Во всяком случае, STL включает класс `vector`. Это C++ шаблон, который можно настроить во время компиляции так, чтобы он содержал только один определённый тип объекта (в данном случае это объект типа `Pair`). В отличие от Java `Vector`, если мы попытаемся поместить что-то кроме объекта типа `Pair` в `vector`, шаблон C++ `vector` вызовет ошибку во время компиляции; в то время как Java `Vector` будет принимать всё подряд. Кроме того, когда вы извлекаете что-либо из `vector`, это автоматически становится объектом типа `Pair`, без необходимости выполнения преобразования. Таким образом, проверка происходит на этапе компиляции, что делает программу более "жёсткой".Также скорость выполнения программы может увеличиться, поскольку нет необходимости выполнять преобразование во время выполнения. Шаблон `vector` также перегружает оператор `[]`, поэтому можно использовать очень удобный синтаксис для извлечения объектов типа `Pair`. Шаблон `vector` будет использоваться при создании `CGI_vector`; тогда станет очевидной мощь такого короткого определения. Если говорить о недостатках, то нельзя забывать о сложности определения `Pair`, как показано в следующем коде. В отличие от того, что мы видим в Java-коде, методы `Pair` значительно более многочисленны. Это связано с тем, что программистам на C++ требуется заранее знать, как контролировать процесс копирования через конструкторы копий и использовать перегруженный оператор присваивания (`operator=`) для выполнения присваивания. Как объясняется в главе 12, иногда нам также приходится иметь дело с аналогичными вопросами в Java. Однако в C++ эти вопросы требуют постоянного внимания.Этот проект начинается с создания повторно используемой части, состоящей из `Pair` и `CGI_vector` в заголовочном файле C++. С точки зрения технологии, действительно лучше не помещать всё это в один заголовочный файл. Однако в данном примере так делать не будет ни к чему вредному, а также это делает его более похожим на стиль Java, поэтому чтение и понимание кода становится проще:```markdown
//: CGITools.h
// Автоматически извлекает и декодирует данные
// из CGI GETs и POSTs. Протестировано с GNU C++.
#include <string.h>
#include <vector> // STL vector
using namespace std;

// Класс для хранения пары имя-значение из запроса CGI.
// CGI_vector хранит объекты Pair и возвращает их через оператор [].
class Pair {
    char* nm;
    char* val;
public:
    Pair() { nm = val = 0; }
    Pair(char* name, char* value) {
        // Создает новую память:
        nm = decodeURLString(name);
        val = decodeURLString(value);
    }
    const char* name() const { return nm; }
    const char* value() const { return val; }
    // Проверка на "пустоту"
    bool empty() const {
        return (nm == 0) || (val == 0);
    }
    // Автоматическое преобразование типа для логического теста:
    operator bool() const {
        return (nm != 0) && (val != 0);
    }
    // Следующие конструкторы и деструктор необходимы для учета состояния в C++.
    // Конструктор копий:
    Pair(const Pair& p) {
        if(p.nm == 0 || p.val == 0) {
            nm = val = 0;
        } else {
            // Создание хранилища и копирование значений справочной стороны:
            nm = new char[strlen(p.nm) + 1];
            strcpy(nm, p.nm);
            val = new char[strlen(p.val) + 1];
            strcpy(val, p.val);
        }
    }
    // Оператор присваивания:
    Pair& operator=(const Pair& p) {
        // Чистка старых lvalues:
        delete nm;
        delete val;
        if(p.nm == 0 || p.val == 0) {
            nm = val = 0;
        } else {
            // Создание хранилища и копирование значений справочной стороны:
            nm = new char[strlen(p.nm) + 1];
            strcpy(nm, p.nm);
            val = new char[strlen(p.val) + 1];
            strcpy(val, p.val);
        }
        return *this;
    }
    ~Pair() { // Деструктор
        delete nm; // Значение 0 допустимо
        delete val;
    }
}
``````cpp
  // Если вы используете этот метод вне этого класса, вы отвечаете за вызов 'delete'
   // для указателя, который был возвращён:
   static char*
   decodeURLString(const char* URLstr) {
     int len = strlen(URLstr);
     char* result = new char[len + 1];
     memset(result, 0, len + 1);
     for(int i = 0, j = 0; i < len; i++, j++) {
       if(URLstr[i] == '+')
         result[j] = ' ';
       else if(URLstr[i] == '%') {
         result[j] =
           translateHex(URLstr[i + 1]) * 16 +
           translateHex(URLstr[i + 2]);
         i += 2; // Переходим после шестнадцатеричного кода
       } else // Обычный символ
         result[j] = URLstr[i];
     }
     return result;
   }
   // Преобразование одного шестнадцатеричного символа; используется в decodeURLString():
   static char translateHex(char hex) {
     if(hex >= 'A')
       return (hex & 0xdf) - 'A' + 10;
     else
       return hex - '0';
   }
 };
 
 // Парсит любой CGI запрос и преобразует его
 // в STL вектор объектов Pair:
 class CGI_vector : public vector<Pair> {
   char* qry;
   const char* start; // Сохраняет начальную позицию
   // Предотвращает присваивание и конструктор копий:
   void operator=(const CGI_vector&);
   CGI_vector(const CGI_vector&);
 public:
   // Константные поля должны быть инициализированы в списке
   // инициализаторов конструктора C++:
   CGI_vector(char* query) :
       start(new char[strlen(query) + 1]) {
     qry = (char*)start; // Преобразование к незаконченной версии
     strcpy(qry, query);
     Pair p;
     while((p = nextPair()) != Pair())
       push_back(p);
   }
   // Деструктор:
   ~CGI_vector() { delete start; }
 private:
   // Производит пары имя-значение из строки запроса
   // Возвращает пустую Pair, когда больше нет строки запроса:
   Pair nextPair() {
     char* name = qry;
     if(name == nullptr || *name == '\0')
       return Pair(); // Конец, возвращаем пустую Pair
     char* value = strchr(name, '=');
     if(value == nullptr)

Корректировки:

  1. Удалены лишние символы = в сравнении != и заменены на правильные операторы сравнения.
  2. Исправлено использование nullptr вместо 0.
  3. Исправлены ошибки в строках memset, где было указано неверное количество байтов для заполнения.
  4. Исправлены логические ошибки в цикле for и условии выхода из него.
  5. Исправлены ошибки в условиях проверки значений указателей name и value. return Pair(); // Ошибка, возвращаем пустую Pair // Нулевым-терминируем имя, перемещаем значение к началу // своего набора символов: *value = '\0'; value++; // Ищем конец значения, отмеченное символом '&': qry = strchr(value, '&'); if(qry == 0) qry = ""; // Последняя пара найдена else { *qry = '\0'; // Терминируем строку значения qry++; // Перемещаемся к следующей паре } return Pair(name, value); } }; ///:~Класс Pair кажется异常简单,仅包含两个私有的字符指针——一个指向名称,另一个指向值。默认构造函数简单地将这两个指针设置为零。这是因为在C++中,对象的内存不会自动清零。第二个构造函数调用了方法decodeURLString(),在新分配的堆内存中生成了一个解码过的字符串。该内存区域必须由对象管理和清理,这一点与我们在「析构器」中看到的一致。「name()」和「value()」方法为相关的字段提供了只读指针。通过「empty()」方法,我们可以查询「Pair」对象中的某个字段是否为空;返回的结果是一个「bool」——C++内置的基本布尔数据类型。「操作符重载」是一种特殊的C++形式,它允许我们控制自动类型转换。如果有名为「p」的「Pair」对象,并且在一个期望布尔结果的表达式中使用,例如「if(p) { // ... }」,那么编译器能够识别到它有一个「Pair」并且需要一个布尔值,因此会自动调用「operator bool()」进行必要的转换。接下来的三个方法属于基本构造函数,它们在创建类时必须被定义。根据所谓的「经典」C++方法,我们需要定义必需的基础构造函数、拷贝构造函数以及赋值运算符(operator=)——以及析构函数以释放内存。Эти определения требуются потому, что компилятор будет "тайно" вызывать их. Конструктор копий вызывается при передаче объекта в функцию или его выходе из функции; оператор присваивания вызывается при распределении объекта. Только полное понимание работы конструктора копий и оператора присваивания позволяет создать действительно "надёжные" классы в C++, но это требует значительных усилий (примечание ⑤).⑤: В моей книге "Thinking in C++" (Prentice-Hall, 1995) целая глава посвящена этому вопросу. Для получения дополнительной информации рекомендую обратиться к этой главе.

Конструктор копий Pair(const Pair&) автоматически вызывается каждый раз, когда объект передается по значению в функцию или выходит из неё. Это значит, что мы не передаем адрес объекта внутри функции, а создаем его полную копию. Это не является опцией в Java, поскольку мы можем передавать только ссылки, поэтому в Java нет конструктора копий (если требуется локальная копия, можно "клонировать" объект — используя clone(), см. главу 12). Аналогично, если в Java происходит распределение ссылки, она просто копируется. Однако в C++ присвоение означает полное копирование объекта. В конструкторе копий мы создаём новое пространство хранения и копируем исходные данные. Но для оператора присваивания нам нужно освободить старое пространство хранения до того, как создать новое. То, что мы видим, может быть одним из самых сложных случаев для C++-класса, но именно этот случай служит основанием для аргументации сторонников Java о том, что Java значительно проще C++. В Java можно свободно передавать ссылки, а задачи очистки памяти берут на себя сборщики мусора, что делает процесс гораздо более легким.Однако история не заканчивается здесь. Класс Pair использует char* для nm и val. Самая сложная часть связана с использованием указателей. Если использовать более современный C++ тип данных std::string вместо char*, то ситуация становится значительно проще (хотя не все компиляторы поддерживают std::string). Тогда первая часть класса Pair будет выглядеть следующим образом:

class Pair {
  std::string nm;
  std::string val;

public:
  Pair() {}
  Pair(char* name, char* value) {
    nm = decodeURLString(name);
    val = decodeURLString(value);
  }

  const char* name() const { return nm.c_str(); }
  const char* value() const { return val.c_str(); }

  // Проверка на "пустоту"
  bool empty() const {
    return (nm.length() == 0) || (val.length() == 0);
  }

  // Автоматическое преобразование типа для логического теста:
  operator bool() const {
    return (nm.length() != 0) && (val.length() != 0);
  }
};

(Кроме того, метод decodeURLString() этого класса возвращает объект типа string, а не указатель на char. Мы не обязаны определять конструктор копий, оператор присваивания (operator=) или деструктор, так как компилятор уже сделал это за нас и сделал хорошо. Однако даже если некоторые вещи происходят автоматически, C++-программист должен понимать детали копирования и присваивания.)Класс Pair состоит из двух методов: decodeURLString() и «помощника» метода translateHex(), который будет использоваться внутри decodeURLString(). Обратите внимание, что translateHex() не защищает от злонамеренного ввода пользователя, такого как %1H. После выделения достаточного пространства памяти (которое должно быть освобождено деструктором), decodeURLString() проходит через него, заменяя все + на пробел; все шестнадцатеричные коды (начинающиеся с %) — на соответствующие символы.

CGI_vector используется для анализа и хранения всего CGI GET-запроса. Он наследуется от STL vector, который экземпляррируется для хранения Pair. Наследование в C++ обозначается двоеточием, в то время как в Java используется ключевое слово extends. Кроме того, наследование по умолчанию является приватным, поэтому почти всегда требуется использование ключевого слова public, как показано ниже. Также можно заметить, что CGI_vector имеет конструктор копий и оператор присваивания, но они объявлены как приватные. Это сделано для предотвращения автоматической синхронизации этих функций компилятором (если бы мы не объявили их сами). Однако это также запрещает клиентским программистам передавать CGI_vector по значению или через присваивание.Основной задачей CGI_vector является получение строки QUERY_STRING и её анализ в пары «имя/значение», что требует помощи класса Pair. Сначала он копирует строку в локально выделенную память и использует постоянный указатель start для отслеживания начального адреса (который затем будет использован деструктором для освобождения памяти). Затем он использует свой метод nextPair() для анализа строки в первоначальные пары «имя/значение», разделённые символами = и &. Эти пары передаются конструктору Pair, поэтому nextPair() возвращает объект типа Pair. Затем этот объект добавляется в vector с помощью метода push_back(). Когда nextPair() завершает обработку всей строки QUERY_STRING, он возвращает ноль.Теперь основные инструменты определены, их можно использовать в CGI-программе следующим образом:```cpp //: Listmgr2.cpp // Версия CGI-программы Listmgr.c на C++, // которая извлекает свои входные данные через GET-запрос // из связанного апплета. Также работает как обычная CGI-программа с HTML-формами. #include <stdio.h> #include "CGITools.h" const char* dataFile = "list2.txt"; const char* notify = "Bruce@EckelObjects.com"; #undef DEBUG

Функция `inList()` практически полностью повторяет предыдущую версию, но теперь она предполагает, что все адреса электронной почты заключены в угловые скобки `< >`. При использовании метода GET (который задается внутри метки METHOD в форме FORM, но здесь контролируется способом отправки данных), веб-сервер собирает все данные, расположенные после знака вопроса (`?`), и помещает их в переменную окружения QUERY_STRING (строка запроса). Для чтения этих данных необходимо получить значение QUERY_STRING, что делается с помощью стандартной функции C getenv(). В `main()` обратите внимание на простоту парсинга QUERY_STRING: достаточно передать её конструктору объекта CGI_vector (называемого query), и дальнейшая работа будет выполнена автоматически. Теперь можно извлекать из query названия и значения, рассматривая их как массив (это возможно благодаря тому, что оператор [] перегружен в vector). Отладочный код виден между директивами препроцессора `#ifdef DEBUG` и `#endif`. В настоящее время нам срочно требуется понять некоторые вещи, связанные с CGI.Программы CGI передают свои входные данные одним из двух способов: либо через `QUERY_STRING` во время выполнения GET (этот метод используется в данный момент), либо через стандартный ввод во время выполнения POST. Выходные данные программы CGI отправляются через стандартный вывод, что обычно реализуется с помощью команды `printf()` в C-программах. А куда же идет этот вывод? Он возвращается обратно веб-серверу, где сервер принимает решение, как его обработать. Основанием для этого решения служит заголовочный элемент `Content-Type`. Это значит, что если `Content-Type` не является первым элементом, который видит сервер, он не знает, как обрабатывать полученные данные. Поэтому все наши CGI-программы должны начинаться с вывода заголовка `Content-Type`.В данном случае мы хотим, чтобы сервер просто отправлял всю информацию обратно клиентской программе (то есть нашими программами-кусочками, которые ожидают ответа). Информация должна оставаться нетронутой, поэтому `content-type` устанавливается как `text/plain`. Как только сервер видит этот заголовок, он начинает отправлять все строки обратно клиенту. Таким образом, каждая строка (три для условий ошибки, одна для успешной операции) будет возвращена нашей программе-кусочку.

Мы используем ту же самую процедуру для добавления электронного имени пользователя (имени пользователя). Но в случае скриптов CGI не возникают бесконечные циклы — программа просто отвечает и завершает работу. При каждом запросе CGI программа запускается, реагирует на запрос и затем закрывается. Поэтому процессор не может оказаться в ситуации пустого ожидания, а производительность снижается только при запуске программы и открытии файла. Веб-сервер контролирует запросы CGI таким образом, что затраты сервера сводят эти проблемы к минимуму.Еще одним преимуществом такого подхода является то, что поскольку `Pair` и `CGI_vector` уже определены, большая часть работы выполняется автоматически, поэтому достаточно изменить `main()`, чтобы легко создать свою собственную CGI-программу. Несмотря на то, что маленькие сервисные программы (`Servlet`) становятся все более популярными, C++ остается удобным выбором для создания быстродействующих CGI-программ.## 15. 6. 4 Концепция POST

Использование GET в многих приложениях не вызывает проблем. Однако, GET требует передачи своих данных CGI-программе через окружение. Но если строка запроса GET слишком длинна, некоторые веб-серверы могут использовать все доступное пространство окружения (если длина строки превышает 200 символов, следует начать беспокоиться об этом вопросе). Для решения этой проблемы CGI предлагает альтернативу — POST. При использовании POST данные закодированы и передаются методом, аналогичным GET, но с помощью стандартного ввода. Все, что нам нужно сделать, это определить длину строки запроса, которая уже хранится в переменной окружения `CONTENT_LENGTH`. Как только известна длина, можно выделить необходимое количество памяти и считать нужное количество символов из стандартного ввода. Для управления POST-запросами в CGI-программе можно использовать `Pair` и `CGI_vector`, предоставляемые файлом `CGITools.h`.

Ниже приведён пример программы, который демонстрирует простоту создания такого CGI-приложения. В этом примере используется «чистый» C++, поэтому библиотека `studio.h` заменена на `iostream` (потоки ввода/вывода). Для `iostream` доступны два заранее определённых объекта: `cin`, используемый для соединения с входными данными, и `cout`, используемый для соединения с выходными данными. Есть несколько способов чтения данных из `cin` и записи данных в `cout`.Однако этот пример использует стандартные методы: отправка информации через оператор `<<` в `cout` и использование членской функции (`read()`) для чтения данных из `cin`.```cpp
//: POSTtest.cpp
// CGI_vector работает так же легко с POST, как и с GET.
// Написано на «чистом» C++.
#include <iostream.h>
#include "CGITools.h"

void main() {
  cout << "Content-type: text/plain\n" << endl;
  // Для CGI «POST», сервер помещает длину строки содержимого
  // в переменную окружения CONTENT_LENGTH:
  char* clen = getenv("CONTENT_LENGTH");
  if(clen == 0) {
    cout << "Нулевое значение CONTENT_LENGTH" << endl;
    return;
  }
  int len = atoi(clen);
  char* query_str = new char[len + 1];
  cin.read(query_str, len);
  query_str[len] = '\0';
  CGI_vector query(query_str);
  // Тест: вывод всех имен и значений
  for(int i = 0; i < query.size(); i++)
    cout << "query[" << i << "].name() = [" <<
      query[i].name() << "], " <<
      "query[" << i << "].value() = [" <<
      query[i].value() << "]" << endl;
  delete [] query_str; // Освобождение памяти
} ///:~

Функция getenv() возвращает указатель на строку, который представляет собой длину содержимого. Если указатель равен нулю, это означает, что переменная окружения CONTENT_LENGTH ещё не установлена, следовательно, где-то произошла ошибка. В противном случае необходимо преобразовать строку в целое число с помощью ANSI C-библиотечной функции atoi(). Эта длина будет использована вместе с new, чтобы выделить достаточно места для хранения строки запроса (плюс её завершающего нуля). Затем вызывается read() для cin(). Функция read() требует указателя на целевой буфер и количество символов для чтения. После этого добавляется завершающий ноль (null) к query_str, что указывает на конец строки.На данном этапе строка запроса ничем не отличается от строки запроса GET, поэтому она передаётся в конструктор для CGI_vector. Затем можно свободно работать с различными полями, как было сделано ранее. Для тестирования этого программного обеспечения его необходимо скомпилировать в директорию cgi-bin хостового Web-сервера. Затем можно создать простую страницу HTML для тестирования, как показано ниже:

<!DOCTYPE HTML>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html">
<title>Тест стандартной передачи данных методом POST</title>
</head>
<body>
<h1>Тест, использует стандартный метод POST</h1>
<form method="POST" action="/cgi-bin/POSTtest">
<p>Поле 1: <input type="text" name="Field1" value="" size="40"></p>
<p>Поле 2: <input type="text" name="Field2" value="" size="40"></p>
<p>Поле 3: <input type="text" name="Field3" value="" size="40"></p>
<p>Поле 4: <input type="text" name="Field4" value="" size="40"></p>
<p>Поле 5: <input type="text" name="Field5" value="" size="40"></p>
<p>Поле 6: <input type="text" name="Field6" value="" size="40"></p>
<p><input type="submit" name="submit"> </p>
</form>
</body>
</html>

Заполнив этот формуляр и отправив его, вы получите простую текстовую страницу, содержащую распаршенные данные. Это позволит вам узнать работает ли CGI-программа правильно.Конечно, использование апплета для отправки данных может быть более интересным. Однако, отправка данных методом POST представляет собой другой процесс. После обычного вызова CGI-программы необходимо установить прямое соединение с сервером, чтобы вернуть ему строку запроса. Сервер затем выполняет некоторые действия и возвращает строку запроса обратно через стандартный ввод CGI-программе.Чтобы установить прямое соединение с сервером, необходимо получить свой URL и использовать openConnection(), чтобы создать объект URLConnection. Однако, поскольку URLConnection обычно не позволяет отправлять данные, требуется смешно вызвать setDoOutput(true), а также setDoInput(true) и setAllowUserInteraction(false) — примечание ⑥. Наконец, можно вызвать getOutputStream(), чтобы создать объект OutputStream (поток вывода), который затем оборачивается в объект DataOutputStream, что позволяет общаться с ним традиционным образом. Ниже представлен фрагмент программы, который используется для выполнения вышеописанных действий после сбора всех данных из его полей:

//: POSTtest.java
// Апплет, который отправляет свои данные через CGI POST
import java.awt.*;
import java.applet.*;
import java.net.*;
import java.io.*;

Класс POSTtest, расширяющий Applet

Константы и поля

public class POSTtest extends Applet {
  final static int SIZE = 10;
  Button submit = new Button("Отправить");
  TextField[] t = new TextField[SIZE];
  String query = "";
  Label l = new Label();
  TextArea ta = new TextArea(15, 60);

Метод init

Инициализирует компоненты формы.

public void init() {
    Panel p = new Panel();
    p.setLayout(new GridLayout(t.length + 2, 2));
    for(int i = 0; i < t.length; i++) {
      p.add(new Label(
        "Поле " + i + "  ", Label.RIGHT));
      p.add(t[i] = new TextField(30));
    }
    p.add(l);
    p.add(submit);
    add("North", p);
    add("South", ta);
}

Метод action

Обрабатывает действия пользователя, такие как нажатие кнопки "Отправить".```java public boolean action(Event evt, Object arg) { if (evt.target.equals(submit)) { query = ""; ta.setText("");

    // Шифрование запроса из данных полей:
    for (int i = 0; i < t.length; i++) 
        query += "Field" + i + "=" + 
            URLEncoder.encode(t[i].getText().trim()) + 
            "&";

    query += "submit=Submit";

    // Отправка имени с помощью процесса POST CGI:
    try {
        URL u = new URL(getDocumentBase(), "cgi-bin/POSTtest");
        URLConnection urlc = u.openConnection();
        urlc.setDoOutput(true);
        urlc.setDoInput(true);
        urlc.setAllowUserInteraction(false);

        DataOutputStream server = new DataOutputStream(urlc.getOutputStream());

        // Отправка данных
        server.writeBytes(query);
        server.close();

        // Чтение и отображение ответа
        DataInputStream in = new DataInputStream(urlc.getInputStream());
        String s;
        while ((s = in.readLine()) != null) 
            ta.appendText(s + "\n");

        in.close();
    } catch (Exception e) {
        l.setText(e.toString());
    }

} else return super.action(evt, arg);

return true;

}

```Не могу сказать, что полностью понимаю, что здесь происходит. Эти концепции взяты из книги Elliotte Rusty Harold "Java Network Programming", опубликованной O'Reilly в 1997 году. В этой книге он упоминает множество запутывающих багов, присутствующих в Java Networking API. Поэтому, когда вы работаете с этими областями, это уже не просто вопрос написания кода и его выполнения. Нужно быть внимательным к потенциальным ловушкам!```После отправки информации на сервер мы вызываем `getInputStream()`, а затем оборачиваем его в объект типа `DataInputStream`, чтобы иметь возможность читать результат. Одним из моментов, на который следует обратить внимание, является то, что результат отображается как текстовая строка в компоненте `TextArea`. Почему бы просто не использовать `getAppletContext().showDocument(u)`? На самом деле, это одна из таких ловушек. Приведённый выше код работает хорошо, но если попробовать заменить его на `showDocument()`, почти всё будет работать некорректно. То есть, `showDocument()` действительно выполняется, но ответ, полученный от `POSTtest`, имеет значение `Zero CONTENT_LENGTH` (нулевой размер содержимого). Так что неясно, почему `showDocument()` препятствует передаче POST запроса CGI-программе. Мне сложно судить, является ли это ошибкой, которая будет исправлена в будущих версиях, или же это связано с моей недостаточной осведомлённостью (в книгах, которые я прочёл, этот момент раскрыт довольно смутно). Однако, независимо от причины, при условии просмотра возвращённого контента из CGI-программы в текстовом поле, приведённый фрагмент кода будет работать корректно.

Опубликовать ( 0 )

Вы можете оставить комментарий после Вход в систему

1
https://api.gitlife.ru/oschina-mirror/wizardforcel-thinking-in-java-zh.git
git@api.gitlife.ru:oschina-mirror/wizardforcel-thinking-in-java-zh.git
oschina-mirror
wizardforcel-thinking-in-java-zh
wizardforcel-thinking-in-java-zh
master