CS general Flashcards
Принципы кодировки
UTF-32 – это кодировка, которая переводит все символы в наборы из 32 бит. Это простой алгоритм, но изводящий много места впустую. UTF-16 и UTF-8 являются кодировками с переменной длиной кодирования. Если символ может быть закодирован одним байтом(потому что номер пункта символа очень маленький), UTF-8 закодирует его одним байтом. Если нужно 2 байта, то используется 2 байта. Кодировка сообщает старшими битами, сколькими битами кодируется текущий символ. Такой способ экономит место, но так же и тратит его в случае, если эти сигнальные биты часто используются. UTF-16 является компромиссом: все символы как минимум двухбайтные, но их размер может увеличиваться до 4 байт, если нужно.
Unicode – это огромная таблица соответствия символов и чисел, а различные UTF кодировки определяют, как эти числа переводятся в биты.
любой символ может быть закодирован множеством разных последовательностей бит, и любая последовательность бит может представлять разные символы, в зависимости от используемой кодировки. разные кодировки используют разное число бит на символ и разные значения для кодирования разных символов.
Гениальность UTF-8 в бинарной совместимости с ASCII, которая является де-факто основой для всех кодировок. Все символы ASCII занимают максимум байт в UTF-8 и используют те же биты, что и в ASCII. Иными словами, ASCII может быть отражено 1:1 в UTF-8. Любой символ не из ASCII занимает 2 или более байт в UTF-8. Большинство языков программирования, использующих ASCII в качестве кодировки исходного кода, позволяет включать текст в UTF-8 прямо в текст
Первый бит каждого байта кодирующего символ отвечает не за сам символ, а за определение байта. То есть например если ведущий (первый) бит нулевой, то это значит что для кодирования символа используется всего один байт. Что и обеспечивает совместимость с ASCII.
Для двухбайтовых символов первые три бита должны быть такие — 110
для трех-байтовых символов в первом байте ведущие биты — 1110
в кодировке UTF-16 любой символ юникода может быть закодирован либо двумя, либо четырьмя байтами.
Суррогатная пара — это две кодовые пары используемые для кодирования одного символа (итого 4 байта). Для таких суррогатных пар в таблице юникода отведен специальный диапазон от D800 до DFFF. Это значит, что при преобразовании кодовой пары из байтового вида в шестнадцатиричный вы получаете число из этого диапазона, то перед вами не самостоятельный символ, а суррогатная пара.
Что такое SSH
SSH (от англ. secure shell – безопасная оболочка) это набор программ, которые позволяют регистрироваться на компьютере по сети, удаленно выполнять на нем команды, а также копировать и перемещать файлы между компьютерами. SSH организует защищенное безопасное соединение поверх небезопасных каналов связи.
SSH предоставляет замены традиционным r-командам удаленного доступа с тем отличием, что они обладают повышенной безопасностью. Они выполняются поверх защищенных зашифрованных соединений, которые не позволяет прослушивать или подменять трафик. Кроме того, SSH может обеспечивать безопасное соединение для передачи любого другого трафика: например, почтовых сообщений или файлов.
Основные функции SSH
Протокол SSH возник как попытка обезопасить открытые незащищенные соединения. Впоследствии его функции были значительно расширены. Наиболее важными из них являются:
Безопасные команды доступа к хосту. SSH дает возможность выполнять безопасные команды доступа к хосту, такие как ssh (удаленная оболочка), slogin (удаленный вход в систему), scp (удаленное копирование);
X11 Forwarding. SSH предоставляет встроенный механизм для выполнения удаленных клиентов X Window.
Port forwarding. SSH может выполнять переадресацию портов, передавая трафик c одного порта одной машины на другой порт другой машины. При этом передаваемый трафик шифруется;
Обеспечение безопасности SSH
Безопасность протокола достигается использованием нескольких решений, которые сводят к минимуму риск использования соединения:
- Шифрование соединение, которое может выполняться одним из методов, выбранных в процессе переговоров. Шифрованное соединение не позволяет просто перехватить и использовать трафик. Выбор алгоритма шифрования делает систему более гибкой, позволяя не использовать алгоритмы, в которых обнаружены слабые места или которые не может поддерживать одна из сторон;
- Аутентификация сервера выполняется при любом соединении. Это не позволяет выполнить подмену сервера или подмену трафика;
- Аутентификация клиента может выполняться одним из нескольких доступных способов. Это с одной стороны может повысить надежность аутентификации, с другой – делает систему более гибкой и упрощает ее использование;
- Проверка целостности пакетов позволяет отследить любые незаконные изменения в трафике соединения. При обнаружении таких изменений, соединение немедленно разрывается;
- Временные параметры аутентификации не позволяют воспользоваться данными соединения в том, случае, если спустя некоторое время после перехвата оно все-таки было расшифровано. Устаревание обычно происходит через час;
Аутентификация сервера (SSH)
Аутентификация сервера производится при помощи инфраструктуры открытых ключей. Клиент, который хочет установить соединение с сервером шифрует данные известным ему открытым ключом сервера и отправляет их серверу. Сервер должен расшифровать их при помощи известного только ему секретного ключа, и отправить их назад. Так клиент может быть уверен в том, является ли хост тем, за кого себя выдает.
Аутентификация сервера по протоколу SSH выполняется при помощи инфраструктуры открытых ключей. Открытый ключ сервера клиент получает при первом соединении с ним.
Аутентификация сервера дает возможность не полагаться на службу имен и маршрутизацию пакетов. В том случае, если нарушителю удалось подменить запись в DNS или перенаправить IP-пакеты на свой хост, аутентификация не пройдет, поскольку хост не обладает необходимыми секретными ключами.
ssh защищает от
Подмены IP-адресов (IP-spoofing), когда удаленный хост посылает пакеты от имени другого хоста;
Подмены DNS-записей (DNS-spoofing), когда изменяется запись на сервере DNS и в результате соединение устанавливается не с желаемым хостом, а с тем, на который указывает новая запись;
Перехвата открытых паролей и прочих данных, которые передаются в открытом виде и любой, кто имеет физический доступ к каналу, может их узнать.
Аутентификация клиента SSH
Методы аутентификации клиентов, которые использует SSH:
Host-based аутентификация Аутентификация с помощью открытых ключей Kerberos-аутентификация Парольная аутентификация. По хостам. Метод аналогичный используемому в r-командах. В том случае, если соединение устанавливается с привилегированного порта, и файл .rhosts позволяет вход в систему, он разрешается. Этот метод является потенциально небезопасным, рекомендуется не использовать его. Для повышения уровня своей безопасности метод может быть дополнен RSA-аутентификацией клиентского хоста.
Открытый ключ. Клиент отправляет серверу открытый ключ. Если сервер знает его, он просит клиента доказать, что тот знает и секретный ключ тоже. Если клиент может это доказать, значит аутентификация считается успешной.
Керберос. Аутентификация проводится по схеме v5 Kerberos.
Пароль. В самом крайнем случае, если не удалось провести аутентификацию не одним из перечисленных способов, используется традиционная аутентификация при помощи пароля. Принцип аутентификации аналогичен тому, какой, например, используется в Telnet с той разницей, что пароль передается по зашифрованному каналу.
Сервер sshd
Как и в любом сетевом взаимодействии, в SSH-соединении участвуют две стороны: клиент и сервер. Сервер SSH реализован в виде программы sshd.
Сервер sshd является независимой программой, поэтому его запуск нужно производить во время загрузки компьютера стартовыми скриптами. То есть, вызов sshd либо должен осуществляться явно одним из rc-скриптов, либо ссылки на скрипт запуска следует включить в иерархию /etc/rc?.d/.
Для того чтобы сервер sshd автоматически стартовал при запуске компьютера, необходимо добавить его вызов в скрипты загрузки.
Будучи запущенным, демон работает в фоновом режиме и обрабатывает все входящие запросы по порту 22. Для каждого соединения создается новая копия демона, который занимается только его обслуживанием.
Настройка сервера sshd
Конфигурация демона определяется файлом /etc/ssh/sshd_config. Он представляет собой набор действующих строк, пустых строк и строк-комментариев. Действующие строки файла содержат название параметра и его значение.
В конфигурационном файле указывается большое количество параметров, которые можно отнести к нескольким категориям:
Адреса. Интерфейс и порт, к которым привязан демон;
Журнализация. Опции, определяющие то, какая именно информация о работе демона должна регистрироваться и заноситься в журналы системы;
Управление ключами. Информация о том, в каких файлах находятся ключи, участвующие в аутентификации.
Аутентификация. Допустимые схемы аутентификации.
Дополнительные параметры. Опции, управляющие дополнительными функциями SSH, такими как переадресация портов, переадресация X11 и другие
Некоторые конфигурационные параметры могут быть переопределены опциями командной строки при вызове sshd.
Клиент ssh
Программа ssh предназначена для регистрации на удаленном хосте с использование протокола ssh и удаленного выполнения команд. Кроме того ssh позволяет выполнять туннелирование любых TCP-соединений внутри ssh-канала (port forwarding).
Синтаксис программы ssh:
ssh опции хост пользователь@хост команда
ssh подключается к удаленному хосту, используя для этого текущее имя пользователя или, если указано явно, имя пользователь. После этого происходит двусторонняя аутентификация, т.е. удаленный хост доказывает, что он именно тот, за кого себя выдает, а регистрирующийся пользователь в свою очередь доказывает это удаленному хосту.
После этого удаленный хост, выполняет заданную команду, либо если команда отсутствует, запускает командный интерпретатор и предоставляет к нему доступ. После того, как команда выполнена, либо оболочка командного интерпретатора завершила свою работу, соединение завершается.
Программа ssh имеет свой собственный конфигурационный файл, точно такого же формата, как и конфигурационный файл сервера sshd, только в котором используются другие директивы.
Некоторые опции командной строки программы ssh
- v – Выводить отладочную информацию о ходе процесса установки соединения. Настоятельно рекомендуется использовать ключ во время настройки службы.
- f – Перейти в фоновый режим, сразу же после того, как пройден этап регистрации
- l пользователь – Регистрироваться на удаленной системе как пользователь. Вместо ключа -l имяможно указывать перед именем хоста в форме пользователь@хост
- p порт – Порт удаленного хоста, на котором доступен сервис SSH. По умолчанию используется порт 22.
- L порт:хост:хостпорт – Перенаправить порт локального хоста на хостпорт удаленного хоста.
- R порт:хост:хостпорт – Перенаправить порт удаленного локального хоста на хостпорт локального хоста.
- o опции – Явно указать опции, которые перекрывают опции, заданные в конфигурационных файлах ssh.
Создание и установка асимметричных ключей SSH
Аутентификация с использованием открытых ключей проходит следующим образом. Хост, на котором выполняется удаленная регистрация предлагает пользователю аутентифицировать себя. Для этого он пересылает сообщение, зашифрованное известным ему открытым ключом пользователя. Если пользователь расшифрует сообщение, значит он знает секретный ключ, следовательно, является тем за кого себя выдает.
Секретные ключи хранятся в файлах identity, id_dsa и id_rsa в локальном каталоге ~/.ssh (разные файлы для разных алгоритмов шифрования). Открытые ключи должны быть в файлах authorized_keys и authorized_keys2 в каталоге ~/.ssh на удаленном компьютере, на котором производится регистрация. В качестве домашнего рассматривается каталог пользователя, под именем которого выполняется регистрация. Файл authorized_keys используется для хранения открытых ключей для SSH1, а в authorized_keys2 — для SSH2.
ssh дает возможность использования одного из трех различных алгоритмов асимметричного шифрования RSA или DSA.
Типы ключей
rsa1 – Тип ключа RSA1, используемый в SSH версии 1
rsa – Тип ключа RSA, используемый в SSH версии 2
dsa – Тип ключа DSA, используемый в SSH версии 2
Для создания, преобразования и управления ключами аутентификации используется утилита ssh-keygen. Синтаксис программы:
ssh-keygen опции
Программа генерирует пару открытый ключ - секретный ключ. При этом она спрашивает, в какие файлы нужно записать ключи. По умолчанию секретный ключ записывается в ~/.ssh/identity (или ~/.ssh/id_rsa, ~/.ssh/id_dsa), а открытый в ~/.ssh/identity.pub.
Сгенерированный секретный ключ защищается при помощи парольной фразы (passphrase), которую нужно обязательно знать, для того чтобы воспользоваться ключом. Секретная фраза вводится в момент генерирования ключей. Она может быть затем изменена, без повторного копирования ключей.
Можно сгенерировать секретные ключи, в которых парольная фраза будет отсутствовать. Хотя это и упрощает использование ключей, делать этого не рекомендуется, поскольку в этом случае файл с секретным ключом автоматически влечет за собой полный беспрепятственный доступ ко всем системам, где установлена его открытая пара.
Полученный открытый ключ нужно добавить в файлы authorized_keys удаленных хостов, к которым будет производится доступ.
После того, как аутентификация с использование открытых ключей настроена, доступ к удаленному компьютеру можно получить без пароля. Однако для того чтобы воспользоваться локальным секретным ключом нужно ввести парольную фразу.
Некоторые опции командной строки программы ssh-keygen
-t тип – Тип генерируемого ключа. Допустимые типы: rsa, rsa1 и dsa.
-p – Изменить парольную фразу
-l – Показать отпечаток (fingerprint) секретного ключа
[править]Опции ключа в ~/.ssh/authorized_keys
Для каждого открытого ключа в файлах ~/.ssh/authorized_keys можно выставить опции, ограничивающие возможности клиента, аутентифицировавшегося по данному ключу. Опции находятся перед ключем,
from=”список-шаблонов-через-запятую” - разрешает принимать соединения только с хостов, удовлетворяющих одному из шаблонов. В качестве шаблонов поддерживаются символы - ?, * и !
command=”команда” - при аутентификации по данному ключу разрешается запускать только указанную команду, а не то что указано пользователем - например, “echo this key disabled” или “dump -params” и обязательно использовать опцию no-pty для бинарной передачи данных
no-port-forwarding - запретить port-forwarding
no-X11-forwarding - запретить X11
no-user-rc - Запрещает выполнение файла ~/.ssh/rc после аутентификации (в RHEL не работает)
environment=”NAME=VALUE” - Принудительная установка переменных среды окружения. (в RHEL не работает)
no-agent-forwarding - запретить использование agent-forwarding
no-pty - запретить интерактивную оболочку
permitopen=”host:port” - (ограничить переназначение локального порта: ssh -L)
scp
Набор программ SSH позволяют не только выполнять удаленную регистрацию на компьютере для доступа к удаленной строке. Программа scp копирует файлы между хостами, полагаясь при этом на протоколы SSH и используя те же методы аутентификации, что и ssh.
Синтаксис программы scp:
scp опции пользователь@хост1:файл1… пользователь@хост2:файл2
Команда устанавливает защищенное соединение между хостом1 и хостом2 и копирует файл1 хоста1 в файл2 хоста2. Любой из хостов может быть локальным. Если имя хоста не указано, подразумевается локальный хост.
Синтаксис использования команды scp напоминает синтаксис cp. Она так же обрабатывает большое количество аргументов, использует аналогичные ключи для рекурсивного копирования, для копирования атрибутов и т.д.
Опции программы scp
- r – Выполнить рекурсивное копирование каталогов
- p – Сохранить по возможности атрибуты файлов (права доступа, время модификации, время доступа) при копировании
- C – Выполнять сжатие файлов при передаче
- P порт – Соединяться с удаленным компьютером по порту порт
- v – Сообщать отладочную информацию о ходе SSH-соединения
- q – Не выдавать индикатор прогресса
SFTP
SFTP (SSH File Transfer Protocol) — SSH-протокол для передачи файлов. Он предназначен для копирования и выполнения других операций с файлами поверх надёжного и безопасного соединения. Как правило, в качестве базового протокола, обеспечивающего соединение, и используется протокол SSH2, но это не обязательно.
SFTP-сервер встроен в OpenSSH. Он реализуется с помощью программы sftp-server. SFTP-клиент sftp встроен в пакет OpenSSH. В качестве SFTP-клиента для Windows может использоваться WinSCP или PSFTP из пакета PuTTY.
Перенаправление TCP-портов (port forwarding) - SSH
У SSH есть возможность, известная как перенаправление портов (port forwarding), которая позволяет передавать по SSH-соединению данные других протоколов. Перенаправление портов позволяет передать подключение, сделанное на одном конце SSH-соединения передать на другой конец и продолжить сеанс связи оттуда.
Другими словами, перенаправление портов SSH можно рассматривать как особую форму туннелирования TCP-соединений, когда SSH-клиент (или сервер; в зависимости от направления туннелирования; об этом ниже) прослушивает на одном хосте порт, и полученные на него соединения передаёт на вторую сторону, SSH-серверу (или клиенту, соответственно) внутри установленного SSH-соединения; вторая сторона инициирует соединение к серверу назначения уже от своего имени, и передаёт в нём полученные по туннелю данные.
Типичная задача, которая может решаться с помощью перенаправление TCP, это доступ к внутреннему серверу сети снаружи, через шлюз. Представьте, что у вас есть сеть, которая находится за UNIX-шлюзом, смотрящим в Интернет. У вас есть доступ к этому шлюзу по SSH. Вы находитесь в Интернете и хотите попасть на один из внутренних серверов сети, не имеет значения по какому протоколу, но пусть, например, по протоколу RDP. Возможности открыть порт при помощи трансляции адресов у вас на сервере нет (например, потому что у вас там нет прав root’а, а есть только доступ к оболочке).
Для решения такой задачи прекрасно подойдёт перенаправление TCP-порта с помощью SSH. Вы создаёте SSH-соединение на шлюз, внутри которого будет передавать трафик будущего соединения. Вы просите ssh выполнить перенаправление портов, в результате чего он открывает на прослушивание какой-либо локальный порт, обращение на который передаётся SSH-серверу на шлюз, который в свою очередь передаёт его серверу-получателю, то есть на RDP-сервер (причём делает это от своего имени; обратный адрес IP-пакетов будет адресом сервера).
Трафик внутри SSH-туннеля шифруется, поэтому перенапраление портов может быть очень полезным для шифрования данных незащищённых протоколов, например, POP3, или создания небольших виртуальных сетей. С другой стороны, нужно иметь в виду, что с помощью перенаправления портов легко организовать доступ изнутри сети наружу или наоборот, и не забывать об этом особенно при выделении учётных записей с доступом к облочке (по-русски говоря, шелла) на шлюзе (по сути, это равносильно предоставлению доступа ко внутренней сети снаружи).
Перенаправление портов может быть отключено либо в конфигурационном файле программ ssh и sshd, либо на стадии их компиляции. Тем не менее пользователь может иметь собственные программы переадресации, на которые конфигурация ssh и sshd не будет оказывать никакого влияния.
Различают два вида перенаправления портов:
Локальное. Клиент принимает подключение и посылает запрос на инициирование соединения SSH-серверу, который в свою очередь устанавливает незащищённое соединение с адресатом;
Удаленное. Сервер принимает подключение и посылает запрос на инициирование соединения SSH-клиенту.
Фактически два этих режима отличаются только тем, с какой стороны будет инициироваться соединение: со стороны клиента или со стороны сервера.
Главным опциями, переводящими ssh в режим перенаправления портов являются -L (локальное перенаправление) и -R (удаленное перенаправление). Кроме того могут быть дополнительно полезны некоторые другие ключи.
Ключи ssh полезные при перенаправлении портов.
- L порт:хост:хостпорт — Выполнить локальное перенаправление портов. При обращении на порт локальной машины будет создан туннель на порт хостпорт указанного хоста.
- R порт:хост:хостпорт — Выполнить удалённое перенаправление. При обращении на порт удаленной машины будет создан SSH-туннель и соединение будет переадресовано на хостпорт указанного хоста.
- N — Не выполнять команду удалённо. Используется только при перенаправлении портов.
- f — Перейти в фоновый режим работы, сразу же после регистрации на удалённой системе.
SSH через HTTP-прокси
Для этого необходимо чтобы на прокси-сервере был разрешён метод CONNECT. Обычно он разрешён как минимум на 443 порт. Если SSH-сервер с той стороны прокси работает на 443 порту, то обратиться на него через SSH не составит никакого труда. Существует несколько программ для этого, самая популярная — corkskrew.
После того как программа установлена, можно добавить в ~/.ssh/config такие строки:
Host *
ProxyCommand corkscrew http-proxy.example.com 8080 %h %p
Обращения на все хосты будут выполняться теперь через прокси http-proxy.example.com (порт 8080).
Протокол HTTP
HTTP — широко распространённый протокол передачи данных, изначально предназначенный для передачи гипертекстовых документов (то есть документов, которые могут содержать ссылки, позволяющие организовать переход к другим документам).
Протокол HTTP предполагает использование клиент-серверной структуры передачи данных. Клиентское приложение формирует запрос и отправляет его на сервер, после чего серверное программное обеспечение обрабатывает данный запрос, формирует ответ и передаёт его обратно клиенту. После этого клиентское приложение может продолжить отправлять другие запросы, которые будут обработаны аналогичным образом.
Задача, которая традиционно решается с помощью протокола HTTP — обмен данными между пользовательским приложением, осуществляющим доступ к веб-ресурсам (обычно это веб-браузер) и веб-сервером. На данный момент именно благодаря протоколу HTTP обеспечивается работа Всемирной паутины.
Также HTTP часто используется как протокол передачи информации для других протоколов прикладного уровня, таких как SOAP, XML-RPC и WebDAV. В таком случае говорят, что протокол HTTP используется как «транспорт».
API многих программных продуктов также подразумевает использование HTTP для передачи данных — сами данные при этом могут иметь любой формат, например, XML или JSON.
Как правило, передача данных по протоколу HTTP осуществляется через TCP/IP-соединения. Серверное программное обеспечение при этом обычно использует TCP-порт 80 (и, если порт не указан явно, то обычно клиентское программное обеспечение по умолчанию использует именно 80-й порт для открываемых HTTP-соединений), хотя может использовать и любой другой.
Стартовая (начальная) строка запроса для HTTP 1.1 составляется по следующей схеме:
Метод URI HTTP/Версия
Например (такая стартовая строка может указывать на то, что запрашивается главная страница сайта):
GET / HTTP/1.1
URI (Uniform Resource Identifier, унифицированный идентификатор ресурса) — путь до конкретного ресурса (например, документа), над которым необходимо осуществить операцию (например, в случае использования метода GET подразумевается получение ресурса). Некоторые запросы могут не относиться к какому-либо ресурсу, в этом случае вместо URI в стартовую строку может быть добавлена звёздочка (астериск, символ «*»). Например, это может быть запрос, который относится к самому веб-серверу, а не какому-либо конкретному ресурсу. В этом случае стартовая строка может выглядеть так:
OPTIONS * HTTP/1.1
Стартовая строка ответа имеет следующую структуру:
HTTP/Версия Код состояния Пояснение
Версия протокола здесь задаётся так же, как в запросе.
Код состояния (Status Code) — три цифры (первая из которых указывает на класс состояния), которые определяют результат совершения запроса. Например, в случае, если был использован метод GET, и сервер предоставляет ресурс с указанным идентификатором, то такое состояние задаётся с помощью кода 200. Если сервер сообщает о том, что такого ресурса не существует — 404. Если сервер сообщает о том, что не может предоставить доступ к данному ресурсу по причине отсутствия необходимых привилегий у клиента, то используется код 403. Спецификация HTTP 1.1 определяет 40 различных кодов HTTP, а также допускается расширение протокола и использование дополнительных кодов состояний.
Пояснение к коду состояния (Reason Phrase) — текстовое (но не включающее символы CR и LF) пояснение к коду ответа, предназначено для упрощения чтения ответа человеком. Пояснение может не учитываться клиентским программным обеспечением, а также может отличаться от стандартного в некоторых реализациях серверного ПО.
Сам по себе протокол HTTP не предполагает использование шифрования для передачи информации. Тем не менее, для HTTP есть распространённое расширение, которое реализует упаковку передаваемых данных в криптографический протокол SSL или TLS.
Название этого расширения — HTTPS (HyperText Transfer Protocol Secure). Для HTTPS-соединений обычно используется TCP-порт 443. HTTPS широко используется для защиты информации от перехвата, а также, как правило, обеспечивает защиту от атак вида man-in-the-middle — в том случае, если сертификат проверяется на клиенте, и при этом приватный ключ сертификата не был скомпрометирован, пользователь не подтверждал использование неподписанного сертификата, и на компьютере пользователя не были внедрены сертификаты центра сертификации злоумышленника.
Протокол HTTP предполагает достаточно большое количество возможностей для расширения. В частности, спецификация HTTP 1.1 предполагает возможность использования заголовка Upgrade для переключения на обмен данными по другому протоколу. Запрос с таким заголовком отправляется клиентом. Если серверу требуется произвести переход на обмен данными по другому протоколу, то он может вернуть клиенту ответ со статусом «426 Upgrade Required», и в этом случае клиент может отправить новый запрос, уже с заголовком Upgrade.
Такая возможность используется, в частности, для организации обмена данными по протоколу WebSocket (протокол, описанный в спецификации RFC 6455, позволяющий обеим сторонам передавать данные в нужный момент, без отправки дополнительных HTTP-запросов): стандартное «рукопожатие» (handshake) сводится к отправке HTTP-запроса с заголовком Upgrade, имеющим значение «websocket», на который сервер возвращает ответ с состоянием «101 Switching Protocols», и далее любая сторона может начать передавать данные уже по протоколу WebSocket.
принципы IP
IP implements packet-switched networking. It has a concept of hosts, which are machines. The IP protocol specifies how datagrams (packets) are sent between these hosts.
A packet is a chunk of binary data that has a source host and a destination host. An IP network will then simply transmit the packet from the source to the destination. One important aspect of IP is that packets are delivered using best effort. A packet may be lost along the way and never reach the destination. Or it may get duplicated and arrive multiple times at the destination.
Each host in an IP network has an address – the so-called IP address. Each packet contains the source and destination hosts’ addresses. The IP is responsible for routing datagrams – as the IP packet travels through the network, each node (host) that it travels through looks at the destination address in the packet to figure out in which direction the packet should be forwarded.
Today, most packages are still IPv4 (Internet Protocol version 4), where each IPv4 address is 32 bits long. They’re most often written in dotted-decimal notation, like so: 198.51.100.42
The newer IPv6 standard is slowly gaining traction. It has a larger address space: its addresses are 128 bits long. This allows for easier routing as the packets travel through the network. And since there are more available addresses, tricks such as network address translation are no longer necessary. IPv6 addresses are represented in the hexadecimal system and divided into eight groups separated by colons, e.g. 2001:0db8:85a3:0042:1000:8a2e:0370:7334.
IPv4 Header
The header is 20 bytes long (without options, which are rarely used).
The most interesting parts of the header are the source and destination IP addresses. Aside from that, the version field will be set to 4 – it’s IPv4. And the protocol field specifies which protocol the payload is using. TCP’s protocol number is 6. The total length field specified is the length of the entire packet – header plus payload.
IPv6 Header
IPv6 uses addresses that are 128 bits long.
The IPv6 header has a fixed length of 40 bytes.
The source and destination addresses are again the most interesting fields. In IPv6, the next header field specifies what data follows the header. IPv6 allows chaining of headers inside the packet. Each subsequent IPv6 header will also have a next header field until the actual payload is reached. When the next header field, for example, is 6 (TCP’s protocol number), the rest of the packet will be TCP data.
IP fragmentation
In IPv4, packets (datagrams) can get fragmented. The underlying transport layer will have an upper limit to the length of packet it can support. In IPv4, a router may fragment a packet if it gets routed onto an underlying data link for which the packet would otherwise be too big. These packets will then get reassembled at the destination host. The sender can decide to disallow routers to fragment packets, in which case they’ll send a Packet Too Big ICMP back to the sender.
In IPv6, a router will always drop the packet and send back a Packet Too Big ICMP6 message to the sender. The end points use this to do a path MTU discovery to figure out what the optimal so-called maximum transfer unit (MTU) along the path between the two hosts is. Only when the upper layer has a minimum payload size that is too big for this MTU will IPv6 use fragmentation. With TCP over IPv6, this is not the case.
TCP
TCP is built on top of IP. The Transmission Control Protocol provides reliable, ordered, error-checked delivery of a stream of data between programs. With TCP, an application running on one device can send data to an application on another device and be sure that the data arrives there in the same way that it was sent.
With TCP, applications establish connections between each other. A TCP connection is duplex and allows data to flow in both directions. The applications on either end do not have to worry about the data being split up into packets, or the fact that the packet transport is best effort. TCP guarantees that the data will arrive at the other end in pristine condition.
A typical use case of TCP is HTTP. Our web browser (application 1) connects to a web server (application 2). Once the connection is made, the browser can send a request through the connection, and the web server can send a response back through the same connection.
Multiple applications on the same host can use TCP simultaneously. To uniquely identify an application, TCP has a concept of ports. A connection between two applications has a source IP address and a source port on one end, and a destination IP address and a destination port at the other end. This pair of addresses, plus a port for either end, uniquely identifies the connection.
A web server using HTTPS will listen on port 443. The browser will use a so-called ephemeral port as the source port and then use TCP to establish a connection between the two address-port pairs.
TCP runs unmodified on top of both IPv4 and IPv6. The Protocol (IPv4) or Next Header (IPv6) field will be set to 6, which is the protocol number for TCP.
TCP Segments
The data stream that flows between hosts is cut up into chunks, which are turned into TCP segments. The TCP segment then becomes the payload of an IP packet.
Each TCP segment has a header and a payload. The payload is the actual data chunk to be transmitted. The TCP segment header first and foremost contains the source and destination port number – the source and destination addresses are already present in the IP header.
The header also contains sequence and acknowledgement numbers and quite a few other fields which are all used by TCP to manage the connection.
TCP Connections
In TCP, a connection is always established from one host to another. Hence, there are two different roles in connection setup: one end (e.g. the web server) is listening for connections, while the other end (e.g. our app) connects to the listening application (e.g. the web server). The server performs a so-called passive open – it starts listening. The client performs a so-called active open toward the server.
Connection setup happens through a three-way handshake. It works like this:
The client sends a SYN to the server with a random sequence number, A
The server replies with a SYN-ACK with an acknowledgment number of A+1 and a random sequence number, B
The client sends an ACK to the server with an acknowledgement number of B+1 and a sequence number of A+1
SYN is short for synchronize sequence numbers. Once data flows between both ends, each TCP segment has a sequence number. This is how TCP makes sure that all parts arrive at the other end, and that they’re put together in the right order. Before communication can start, both ends need to synchronize the sequence number of the first segments.
ACK is short for acknowledgment. When a segment arrives at one of the ends, that end will acknowledge the receipt of that segment by sending an acknowledgment for the sequence number of the received segment.
When the connection is created, both ends can send data to the other end. Each segment that is sent has a sequence number corresponding to the number of bytes sent so far. The receiving end will acknowledge packets as they are received by sending back segments with the corresponding ACK in the header.
Flow control is about making sure that the sending side doesn’t send data faster than the receiver (at either end) can process it. The receiver sends a so-called receive window, which tells the sender how much more data the receiver can buffer.
The basic idea is that the sender side looks at the acknowledgments it gets back. This is a very tricky business and there are various tradeoffs to be made. Remember that the underlying IP packets can arrive out of order, not at all, or twice. The sender needs to estimate what the round-trip time is and use that to figure out if it should have received an acknowledgement already. Retransmitting packets is obviously costly, but not retransmitting causes the connection to stall for a while, and the load on the network is likely to be very dynamic. The TCP algorithms need to constantly adapt to the current situation.
The important thing to note is that a TCP connection is a very lively and flexible thing. Aside from the actual data flow, both ends constantly send hints and updates back and forth to continuously fine-tune the connection.
Because of this tuning, TCP connections that are short-lived can be very inefficient. When a connection is first created, the TCP algorithms still don’t know what the conditions of the network are. And toward the end of the connection lifetime, there’s less information flowing back to the sender, which therefore has a harder time estimating how things are moving along.
XML
С физической точки зрения документ состоит из сущностей (англ. entities), из которых каждая может отсылать на другую сущность. Единственный корневой элемент — документная сущность. Содержание сущностей — символы.
С логической точки зрения документ состоит из комментариев (англ. comments), объявлений (англ. declarations), элементов (англ. elements), ссылок на сущности (англ. character references) и инструкций обработки (англ. processing instructions). Всё это в документе структуризуется разметкой (англ. markup).
Документ состоит из сущностей, содержание которых — символы. Все они разделены на два типа: символьные данные (англ. character data) и разметки. К разметке принадлежат: теги (англ. tags), обозначающие границы элементов, объявления и инструкции обработки, включая их атрибуты (англ. attributes), ссылки на сущности, комментарии, а также последовательности символов, обрамляющие секции «CDATA». Часть документа, не принадлежащая разметке, составляет символьные данные документа.
Все составляющие части документа обобщаются в пролог и корневой элемент. Корневой элемент — обязательная часть документа, составляющая всю его суть (пролог, вообще говоря, может отсутствовать). Может включать (а может не включать) вложенные в него элементы и символьные данные, а также комментарии. Вложенные в корневой элемент элементы, в свою очередь, могут включать вложенные в них элементы, символьные данные и комментарии, и так далее. Пролог может включать объявления, инструкции обработки, комментарии. Его следует начинать с объявления XML, хотя в определённой ситуации допускается отсутствие этого объявления.
Элементы документа должны быть правильно вложены: любой элемент, начинающийся внутри другого элемента (то есть любой элемент документа, кроме корневого), должен заканчиваться внутри элемента, в котором он начался. Символьные данные могут встречаться внутри элементов как непосредственно так и в специальных секциях «CDATA». Объявления, инструкции обработки и элементы могут иметь связанные с ними атрибуты. Атрибуты используются для связывания с логической единицей текста пар имя-значение.
Объявление XML указывает версию языка, на которой написан документ. Поскольку интерпретация содержимого документа зависит от версии языка, то Спецификация предписывает начинать документ с объявления XML. В первой (1.0) версии языка использование объявления не было обязательным, в последующих версиях оно обязательно. Таким образом, версия языка определяется из объявления, и если объявление отсутствует, то принимается версия 1.0.
Кроме версии XML,объявление может также содержать информацию о кодировке документа
Для объявления типа документа существует специальная инструкция !DOCTYPE. Она позволяет задать при помощи языка DTD, какие в документ входят элементы, каковы их атрибуты, какие сущности могут использоваться и кое-что ещё.
После XML-объявления могут следовать комментарии, инструкции обработки или же пустые пространства[11], но затем идёт Объявления типа документа, где «Name» — имя корневого тега, «ExternalID» — внешний идентификатор, а «intSubset» — объявление разметки или же ссылка на сущность. Как гласит спецификация, если внешний идентификатор объявляется вместе с внутренним объявлением, то последнее идёт перед первым
Элемент (англ. element) является понятием логической структуры документа. Каждый документ содержит один или несколько элементов. Границы элементов представлены начальным и конечным тегами. Имя элемента в начальном и конечном тегах элемента должно совпадать. Элемент может быть также представлен тегом пустого, то есть не включающего в себя другие элементы и символьные данные, элемента.
Для решения задачи преобразования документа XML в другую схему или другой формат предназначен язык XSLT.
Для форматированного документа (документа, подготовленного к визуализации) предназначен формат XSL-FO.
XPath — синтаксис для адресации содержимого документа, представленного в форме дерева. Выражения XPath используются в языке XQuery. Выражения XPath, вообще говоря, могут использоваться в любом контексте, где уместно использовать формальные ссылки на элементы дерева, в частности, в качестве параметров для методов интерфейсов доступа к документу.
XQuery — язык программирования, ориентированный на работу с документами.
Для чтения XML есть три варианта API[13].
Событийный API (event-driven API, push-style API) — XML-процессор читает XML; при определённом событии (появлении открывающего или закрывающего тега, текстовой строки, атрибута) вызывается callback-функция.
+ Расходует мало памяти[13].
+ При обработке огромного XML есть стандартная точка, позволяющая мгновенно остановить обработчик[13].
− Крайне сложен для прикладного программиста: приходится держать в памяти информацию, в каком месте документа мы находимся.
+ Библиотека проста в программировании.
− Затруднена поддержка перекрёстных ссылок: надо организовать временное хранение строковых ссылок, а когда документ будет считан — преобразовать идентификаторы в указатели.
− При ошибке в XML в памяти остаётся полусозданная структура предметной отрасли; программист должен своими руками корректно уничтожить её.
− API только для чтения, для записи потребуется другой API.
± Естественный выбор, когда из огромного XML надо извлечь немного данных[13].
± Естественный выбор, когда XML надо преобразовать в структуру предметной отрасли[13].
Примеры библиотек: SAX, Expat
Потоковый API (также pull-style API) — устроен на манер потоков ввода-вывода.
+ Расходует мало памяти.
± Информация, в каком месте документа мы находимся, неявно задаётся местом в потоке выполнения. Это серьёзно упрощает работу прикладного программиста. На продуманных API объём кода приближается к таковому для DOM.
− Библиотека сложна в программировании.
− Сложно сделать, чтобы «почти верные» XML с перепутанным порядком тегов работали правильно.
− Затруднена поддержка перекрёстных ссылок.
− При ошибке в XML в памяти остаётся полусозданная структура предметной отрасли; программист должен своими руками корректно уничтожить её.
− API только для чтения, для записи потребуется другой API.
Примеры библиотек: StAX
Объектный API (Document Object Model, DOM, «объектная модель документа») — считывает XML и воссоздаёт его в памяти в виде объектной структуры.
− Расходует много памяти — намного больше, чем сам XML занимает на диске. На pugixml расход памяти втрое и более превышает длину XML.
+ Прост для прикладного программиста.
+ Библиотека проста в программировании.
+ Зачастую удаётся распознать «почти верные» XML с перепутанным порядком тегов.
+ Позволяет произвольный доступ к XML[13]. Это, например, упрощает работу с перекрёстными ссылками.
+ При ошибке в XML в памяти остаётся полусозданная структура XML, которая будет автоматически уничтожена самой библиотекой.
+ Общий API для чтения и записи.
± Естественный выбор, когда объектом предметной области является сам XML: например, в веб-браузере[13], XML-редакторе, в импортёре к программе-локализатору, который извлекает строки из XML произвольной структуры.
± Естественный выбор, когда нужно загрузить XML, слегка переработать и сохранить[13]. Те части, которые трогать не нужно, не требуют никакого кода.
Примеры библиотек: JDOM, TinyXML, pugixml
API прямой записи записывает XML тег за тегом, атрибут за атрибутом.
+ Быстр, нет промежуточных объектов.
− Примитивная библиотека может делать неоптимальный XML (например, вместо ). Работающая оптимально намного сложнее в программировании.
− Непригоден для отдельных специфических задач.
− Если структуры предметной области работают ненадёжно, без специальных мер (записать в память или в другой файл, потом переименовать) можно остаться с «упавшей» программой и потерянным файлом.
− При ошибке программиста может получиться синтаксически некорректный XML.
− API только для записи, для чтения потребуется другой API.
Объектный API, он же Document Object Model.
− Создаёт объектную структуру для XML, что может отнять памяти больше, чем структура предметной отрасли.
± Универсален (впрочем, в большинстве задач преимущества над хорошо проработанным API прямой записи нет — в отличие от чтения).
+ Даже если структуры предметной области работают ненадёжно, а программист не предусмотрел никакой «защиты», единственный сценарий, когда файл перезаписывается на неполный — нехватка места на диске.
+ Общий API для записи и чтения.
Примеры библиотек: те же, что и для чтения XML методом DOM.
XML — язык разметки, другими словами, средство описания документа. Именно в нише документов, текстов, где доля разнотипных символьных данных велика, а доля разметки мала — XML успешен. С другой стороны, обмен данными в открытых системах не сводится к обмену документами. Избыточность разметки XML (а в целях разработки языка прямо указано, что лаконичность не является приоритетом проекта) сказывается в ситуациях, когда данные не вписываются в традиционную модель документа.
HTML
Язык HTML интерпретируется браузерами; полученный в результате интерпретации форматированный текст отображается на экране монитора компьютера или мобильного устройства.
Язык HTML до 5-й версии определялся как приложение SGML (стандартного обобщённого языка разметки по стандарту ISO 8879). Спецификации HTML5 формулируются в терминах DOM (объектной модели документа).
Язык XHTML является более строгим вариантом HTML, он следует синтаксису XML и является приложением языка XML в области разметки гипертекста.
Во всемирной паутине HTML-страницы, как правило, передаются браузерам от сервера по протоколам HTTP или HTTPS, в виде простого текста или с использованием шифрования.
Любой документ на языке HTML представляет собой набор элементов, причём начало и конец каждого элемента обозначается специальными пометками — тегами. Элементы могут быть пустыми, то есть не содержащими никакого текста и других данных. В этом случае обычно не указывается закрывающий тег (например, тег переноса строки <br></br> — одиночный и закрывать его не нужно) . Кроме того, элементы могут иметь атрибуты, определяющие какие-либо их свойства (например, атрибут href=” у ссылки). Атрибуты указываются в открывающем теге.
Кроме элементов, в HTML-документах есть и сущности (англ. entities) — «специальные символы». Сущности начинаются с символа амперсанда и имеют вид &имя; или NNNN;, где NNNN — код символа в Юникоде в десятичной системе счисления.
Каждый HTML-документ, отвечающий спецификации HTML какой-либо версии, должен начинаться со строки объявления версии HTML >
Далее обозначается начало и конец документа тегами и соответственно. Внутри этих тегов должны находиться теги заголовка () и тела () документа.
Язык разметки HTML включает поддержку клиентских скриптов (сценариев), которые могут быть выполнены во время загрузки документа или позже. Скрипты могут быть как внешними (js-файлы), так и внутренними (элемент или атрибуты обработчиков событий в самих элементах).
Элемент
может располагаться либо в <head>, либо в <body>-элементе (перед закрывающим </body>).
Скрипты используются, например, для обработки событий от клавиатуры, мыши, событий от форм, общего состояния документа.
Форма задаётся с помощью элемента <form>, внутри которого и располагаются элементы управления. Кроме общих для HTML атрибутов, в <form> могут присутствовать следующие:
action (действие) — обязательный атрибут (в HTML5 — нет), содержащий URI обработчика формы;
method (метод отправки формы) — атрибут, принимающий значения GET (по умолчанию) или POST;
enctype (тип кодирования для содержимого) — по умолчанию application/x-www-form-urlencoded (всегда для метода GET), но обычно употребляется multipart/form-data;
accept — список MIME-типов для загрузки файлов;
name — имя формы;
onsubmit — обработчик события «форма отправлена» (для скриптов);
onreset — обработчик события: «форма очищена» (тоже для скриптов);
accept-charset список поддерживаемых наборов символов.
JSON
JavaScript Object Notation
За счёт своей лаконичности по сравнению с XML формат JSON может быть более подходящим для сериализации сложных структур. Применяется в веб-приложениях как для обмена данными между браузером и сервером (AJAX), так и между серверами (программные HTTP-сопряжения).
Поскольку формат JSON является подмножеством синтаксиса языка JavaScript, то он может быть быстро десериализован встроенной функцией eval().
JSON-текст представляет собой (в закодированном виде) одну из двух структур:
Набор пар ключ: значение. В различных языках это реализовано как запись, структура, словарь, хеш-таблица, список с ключом или ассоциативный массив. Ключом может быть только строка (регистрозависимая: имена с буквами в разных регистрах считаются разными[4]), значением — любая форма.
Упорядоченный набор значений. Во многих языках это реализовано как массив, вектор, список или последовательность.
Структуры данных, используемые JSON, поддерживаются любым современным языком программирования, что и позволяет применять JSON для обмена данными между различными языками программирования и программными системами.
В качестве значений в JSON могут быть использованы:
запись — это неупорядоченное множество пар ключ:значение, заключённое в фигурные скобки «{ }». Ключ описывается строкой, между ним и значением стоит символ «:». Пары ключ-значение отделяются друг от друга запятыми.
массив (одномерный) — это упорядоченное множество значений. Массив заключается в квадратные скобки «[ ]». Значения разделяются запятыми. Массив может быть пустым, т.е. не содержать ни одного значения.
число (целое или вещественное).
литералы true (логическое значение «истина»), false (логическое значение «ложь») и null.
строка — это упорядоченное множество из нуля или более символов юникода, заключённое в двойные кавычки. Символы могут быть указаны с использованием escape-последовательностей, начинающихся с обратной косой черты «\» (поддерживаются варианты ', ", \, \/, \t, \n, \r, \f и \b), или записаны шестнадцатеричным кодом в кодировке Unicode в виде \uFFFF.
Строка очень похожа на литерал одноимённого типа данных в языке Javascript. Число тоже очень похоже на Javascript-число, за исключением того, что используется только десятичный формат (с точкой в качестве разделителя). Пробелы могут быть вставлены между любыми двумя синтаксическими элементами.
nginx
веб-сервер и почтовый прокси-сервер, работающий на Unix-подобных операционных системах
Nginx позиционируется производителем как простой, быстрый и надёжный сервер, не перегруженный функциями.
Применение nginx целесообразно прежде всего для статических веб-сайтов и как обратного прокси-сервера перед динамическими сайтами
HTTP-сервер
обслуживание неизменяемых запросов, индексных файлов, автоматическое создание списка файлов, кеш дескрипторов открытых файлов
акселерированное проксирование без кэширования, простое распределение нагрузки и отказоустойчивость
поддержка кеширования при акселерированном проксировании и FastCGI
акселерированная поддержка FastCGI и memcached-серверов, простое распределение нагрузки и отказоустойчивость
модульность, фильтры, в том числе сжатие (gzip), byte-ranges (докачка), chunked-ответы, HTTP-аутентификация, SSI-фильтр
несколько подзапросов на одной странице, обрабатываемые в SSI-фильтре через прокси или FastCGI, выполняются параллельно
поддержка SSL
поддержка PSGI, WSGI
экспериментальная поддержка встроенного Perl
SMTP/IMAP/POP3-прокси сервер
перенаправление пользователя на SMTP/IMAP/POP3-бэкенд с использованием внешнего HTTP-сервера аутентификации
простая аутентификация (LOGIN, USER/PASS)
поддержка SSL и STARTTLS
В nginx рабочие процессы обслуживают одновременно множество соединений, мультиплексируя их вызовами операционной системы select, epoll (Linux) и kqueue (FreeBSD). Рабочие процессы выполняют цикл обработки событий от дескрипторов (см. Событийно-ориентированное программирование). Полученные от клиента данные разбираются с помощью конечного автомата. Разобранный запрос последовательно обрабатывается цепочкой модулей, задаваемой конфигурацией. Ответ клиенту формируется в буферах, которые хранят данные либо в памяти, либо указывают на отрезок файла. Буфера объединяются в цепочки, определяющие последовательность, в которой данные будут переданы клиенту. Если операционная система поддерживает эффективные операции ввода-вывода, такие как writev и sendfile, то nginx применяет их по возможности.
У nginx есть один главный и несколько рабочих процессов. Основная задача главного процесса — чтение и проверка конфигурации и управление рабочими процессами. Рабочие процессы выполняют фактическую обработку запросов. nginx использует модель, основанную на событиях, и зависящие от операционной системы механизмы для эффективного распределения запросов между рабочими процессами. Количество рабочих процессов задаётся в конфигурационном файле и может быть фиксированным для данной конфигурации или автоматически устанавливаться равным числу доступных процессорных ядер
Как работают nginx и его модули, определяется в конфигурационном файле. По умолчанию, конфигурационный файл называется nginx.conf и расположен в каталоге /usr/local/nginx/conf, /etc/nginx или /usr/local/etc/nginx.
Чтобы запустить nginx, нужно выполнить исполняемый файл. Когда nginx запущен, им можно управлять, вызывая исполняемый файл с параметром -s. Используйте следующий синтаксис:
nginx -s сигнал
Где сигнал может быть одним из нижеследующих:
stop — быстрое завершение
quit — плавное завершение
reload — перезагрузка конфигурационного файла
reopen — переоткрытие лог-файлов
Получив сигнал, главный процесс проверяет правильность синтаксиса нового конфигурационного файла и пытается применить конфигурацию, содержащуюся в нём. Если это ему удаётся, главный процесс запускает новые рабочие процессы и отправляет сообщения старым рабочим процессам с требованием завершиться. В противном случае, главный процесс откатывает изменения и продолжает работать со старой конфигурацией. Старые рабочие процессы, получив команду завершиться, прекращают принимать новые запросы и продолжают обслуживать текущие запросы до тех пор, пока все такие запросы не будут обслужены. После этого старые рабочие процессы завершаются.
Посылать сигналы процессам nginx можно также средствами Unix, такими как утилита kill. В этом случае сигнал отправляется напрямую процессу с данным ID. ID главного процесса nginx записывается по умолчанию в файл nginx.pid в каталоге /usr/local/nginx/logs или /var/run.
Для просмотра списка всех запущенных процессов nginx может быть использована утилита ps, например, следующим образом:
ps -ax | grep nginx
nginx состоит из модулей, которые настраиваются директивами, указанными в конфигурационном файле. Директивы делятся на простые и блочные. Простая директива состоит из имени и параметров, разделённых пробелами, и оканчивается точкой с запятой (;). Блочная директива устроена так же, как и простая директива, но вместо точки с запятой после имени и параметров следует набор дополнительных инструкций, помещённых внутри фигурных скобок ({ и }). Если у блочной директивы внутри фигурных скобок можно задавать другие директивы, то она называется контекстом (примеры: events, http, server и location).
Директивы, помещённые в конфигурационном файле вне любого контекста, считаются находящимися в контексте main. Директивы events и http располагаются в контексте main, server — в http, а location — в server.
Часть строки после символа # считается комментарием.
В общем случае конфигурационный файл может содержать несколько блоков server, различаемых по портам, на которых они слушают, и по имени сервера. Определив, какой server будет обрабатывать запрос, nginx сравнивает URI, указанный в заголовке запроса, с параметрами директив location, определённых внутри блока server.
Итоговая конфигурация блока server должна выглядеть следующим образом:
server {
location / {
root /data/www;
}
location /images/ { root /data; } } Это уже работающая конфигурация сервера, слушающего на стандартном порту 80 и доступного на локальном компьютере по адресу http://localhost/. В ответ на запросы, URI которых начинаются с /images/, сервер будет отправлять файлы из каталога /data/images. Например, на запрос http://localhost/images/example.png nginx отправит в ответ файл /data/images/example.png. Если же этот файл не существует, nginx отправит ответ, указывающий на ошибку 404. Запросы, URI которых не начинаются на /images/, будут отображены на каталог /data/www. Например, в результате запроса http://localhost/some/example.html в ответ будет отправлен файл /data/www/some/example.html.
Одним из частых применений nginx является использование его в качестве прокси-сервера, то есть сервера, который принимает запросы, перенаправляет их на проксируемые сервера, получает ответы от них и отправляет их клиенту.
Во-первых, создайте проксируемый сервер, добавив ещё один блок server в конфигурационный файл nginx со следующим содержимым:
server {
listen 8080;
root /data/up1;
location / { } } Это будет простой сервер, слушающий на порту 8080 (ранее директива listen не указывалась, потому что использовался стандартный порт 80) и отображающий все запросы на каталог /data/up1 в локальной файловой системе. Создайте этот каталог и положите в него файл index.html. Обратите внимание, что директива root помещена в контекст server. Такая директива root будет использована, когда директива location, выбранная для выполнения запроса, не содержит собственной директивы root. Далее, используйте конфигурацию сервера из предыдущего раздела и видоизмените её, превратив в конфигурацию прокси-сервера. В первый блок location добавьте директиву proxy_pass, указав протокол, имя и порт проксируемого сервера в качестве параметра (в нашем случае это http://localhost:8080):
server {
location / {
proxy_pass http://localhost:8080;
}
location /images/ { root /data; } } Мы изменим второй блок location, который на данный момент отображает запросы с префиксом /images/ на файлы из каталога /data/images так, чтобы он подходил для запросов изображений с типичными расширениями файлов. Изменённый блок location выглядит следующим образом:
location ~ .(gif|jpg|png)$ {
root /data/images;
}
Параметром является регулярное выражение, дающее совпадение со всеми URI, оканчивающимися на .gif, .jpg или .png. Регулярному выражению должен предшествовать символ ~. Соответствующие запросы будут отображены на каталог /data/images.
Когда nginx выбирает блок location, который будет обслуживать запрос, то вначале он проверяет директивы location, задающие префиксы, запоминая location с самым длинным подходящим префиксом, а затем проверяет регулярные выражения. Если есть совпадение с регулярным выражением, nginx выбирает соответствующий location, в противном случае берётся запомненный ранее location.
nginx можно использовать для перенаправления запросов на FastCGI-серверы. На них могут исполняться приложения, созданные с использованием разнообразных фреймворков и языков программирования, например, PHP.
Базовая конфигурация nginx для работы с проксируемым FastCGI-сервером включает в себя использование директивы fastcgi_pass вместо директивы proxy_pass, и директив fastcgi_param для настройки параметров, передаваемых FastCGI-серверу. Представьте, что FastCGI-сервер доступен по адресу localhost:9000. Взяв за основу конфигурацию прокси-сервера из предыдущего раздела, замените директиву proxy_pass на директиву fastcgi_pass и измените параметр на localhost:9000. В PHP параметр SCRIPT_FILENAME используется для определения имени скрипта, а в параметре QUERY_STRING передаются параметры запроса.
Agile
- разработка ведется короткими циклами (итерациями), продолжительностью 1-4 недели;
- в конце каждой итерации заказчик получает ценное для него приложение (или его часть), которое можно использовать в бизнесе;
- команда разработки сотрудничает с Заказчиком в ходе всего проекта;
- изменения в проекте приветствуются и быстро включаются в работу.
Scrum
В скрам используется всего четыре артефакта:
Product Backlog
Sprint Backlog
Sprint Goal
Sprint Burndown Chart.
Product backlog:
Это список всех требований, которые нужно сделать по проекту. Когда в Backlog’e нет требований, проект считается завершенным.
Все требования описаны по единому шаблону, который называют User Story (пользовательская история).
Требования составлены так, что очевидно и понятно, какую ценность они представляют для пользователя
Требования отсортированы по приоритетам, которые пересматриваются каждый спринт.
Sprint backlog:
Это список всех требований, которые нужно сделать в ближайший спринт.
В течение спринта, новые требования не могут появится в Sprint backlog.
Все требования должны быть разделены на задачи и оценены.
Sprint Backlog – это обязательство команды: что они должны выполнить за ближайшие 2 недели. Каждое требование разделено на задачи, которые представлены на Kanban-доске.
Sprint Goal
это краткое описание того, ради чего выполняется данный спринт.
цель на спринт помогает команде принимать обоснованные решения.
Этот артефакт необходим для того, чтобы команда проекта могла самостоятельно принимать решение в случае появления альтернативных путей решения задачи. Чтобы решения команды были осознанными, Product Owner определяет цель спринта.
Sprint Burndown Chart
дословно “диаграмма сгорания”
в качестве “сгорающих” элементов выступают человеко-часы или идеальные единицы (Story Points).
диаграмма обновляется каждый раз, когда завершается какая-либо задача.
Рефакторинг
Рефакторинг представляет собой процесс такого изменения программной системы, при котором не меняется внешнее поведение кода, но улучшается его внутренняя структура. Это способ систематического приведения кода в порядок, при котором шансы появления новых ошибок минимальны. В сущности, при проведении рефакторинга кода вы улучшаете его дизайн уже после того, как он написан.
Правила рефакторинга
Обнаружив, что в программу необходимо добавить новую функциональность, но код программы не структурирован удобным для добавления этой функциональности образом, сначала произведите рефакторинг программы, чтобы упростить внесение необходимых изменений, а только потом добавьте функцию.
Перед началом рефакторинга убедитесь, что располагаете надежным комплектом тестов. Эти тесты должны быть самопроверяющимися.
При применении рефакторинга программа модифицируется небольшими шагами. Ошибку нетрудно обнаружить.
Написать код, понятный компьютеру, может каждый, но только хорошие программисты пишут код, понятный людям.
Рефакторинг улучшает композицию программного обеспечения, облегчает понимание программного обеспечения, помогает найти ошибки, позволяет быстрее писать программы
Применяйте рефакторинг при добавлении новой функции, если требуется исправить ошибку, при разборе кода
Методы рефакторинга
Инкапсуляция поля (Encapsulate Field);
Выделение класса (Extract Class);
Выделение интерфейса (Extract Interface);
Выделение локальной переменной (Extract Local Variable);
Выделение метода (Extract Method);
Генерализация типа (Generalize Type);
Встраивание (Inline);
Введение фабрики (Introduce Factory);
Введение параметра (Introduce Parameter);
Подъём поля/метода (Pull Up);
Спуск поля/метода (Push Down);
Замена условного оператора полиморфизмом (Replace Conditional with Polymorphism);
принципы ООП
- Данные структурируются в виде объектов, каждый из которых имеет определенный тип, то есть принадлежит к какому-либо классу.
- Классы – результат формализации решаемой задачи, выделения главных ее аспектов.
- Внутри объекта инкапсулируется логика работы с относящейся к нему информацией.
- Объекты в программе взаимодействуют друг с другом, обмениваются запросами и ответами.
При этом объекты одного типа сходным образом отвечают на одни и те же запросы. - Объекты могут организовываться с более сложные структуры, например, включать другие объекты или наследовать от одного или нескольких объектов.
Главное
- Инкапсулируйте все, что может изменяться;
- Уделяйте больше внимания интерфейсам, а не их реализациям;
- Каждый класс в вашем приложении должен иметь только одно назначение;
- Классы — это их поведение и функциональность.
Базовые принципы ООП
- Абстракция — отделение концепции от ее экземпляра;
- Полиморфизм — реализация задач одной и той же идеи разными способами;
- Наследование — способность объекта или класса базироваться на другом объекте или классе. Это главный механизм для повторного использования кода. Наследственное отношение классов четко определяет их иерархию;
- Инкапсуляция — размещение одного объекта или класса внутри другого для разграничения доступа к ним.
Используйте следующее вместе с наследованием
- Делегация — перепоручение задачи от внешнего объекта внутреннему;
- Композиция — включение объектом-контейнером объекта-содержимого и управление его поведением; последний не может существовать вне первого;
- Агрегация — включение объектом-контейнером ссылки на объект-содержимое; при уничтожении первого последний продолжает существование
.
Не повторяйся (Don’t repeat yourself — DRY)
Избегайте повторного написания кода, вынося в абстракции часто используемые задачи и данные. Каждая часть вашего кода или информации должна находиться в единственном числе в единственном доступном месте.
Принцип единственной обязанности
Для каждого класса должно быть определено единственное назначение. Все ресурсы, необходимые для его осуществления, должны быть инкапсулированы в этот класс и подчинены только этой задаче.
Принцип открытости/закрытости
Программные сущности должны быть открыты для расширения, но закрыты для изменений.
Принцип подстановки Барбары Лисков
Методы, использующие некий тип, должны иметь возможность использовать его подтипы, не зная об этом.
Принцип разделения интерфейсов
Предпочтительнее разделять интерфейсы на более мелкие тематические, чтобы реализующие их классы не были вынуждены определять методы, которые непосредственно в них не используются.
Принцип инверсии зависимостей
Система должна конструироваться на основе абстракций “сверху вниз”: не абстракции должны формироваться на основе деталей, а детали должны формироваться на основе абстракций.
Императивное и декларативное программирование
Императивный подход (как) - дается набор шагов, которые необходимо выполнить
Декларативный подход (что) - дается конечная цель: предполагается, что уже есть знания и инструменты, как ее достичь
многие декларативные подходы имеют определённый слой императивных абстракций
Императивные: C, C++, Java.
Декларативные: SQL, HTML.
Смешанные (могут быть таковыми): JavaScript, C#, Python.
Замыкание
замыкание — функция, которая ссылается на свободные переменные в своей области видимости.
Замыкание, так же как и экземпляр объекта, есть способ представления функциональности и данных, связанных и упакованных вместе.
Замыкание — это особый вид функции. Она определена в теле другой функции и создаётся каждый раз во время её выполнения. Синтаксически это выглядит как функция, находящаяся целиком в теле другой функции. При этом вложенная внутренняя функция содержит ссылки на локальные переменные внешней функции. Каждый раз при выполнении внешней функции происходит создание нового экземпляра внутренней функции, с новыми ссылками на переменные внешней функции.
В случае замыкания ссылки на переменные внешней функции действительны внутри вложенной функции до тех пор, пока работает вложенная функция, даже если внешняя функция закончила работу, и переменные вышли из области видимости.[1]
Замыкание связывает код функции с её лексическим окружением (местом, в котором она определена в коде). Лексические переменные замыкания отличаются от глобальных переменных тем, что они не занимают глобальное пространство имён. От переменных в объектах они отличаются тем, что привязаны к функциям, а не объектам
# Реализация с помощью именованных функций: def make_adder(x): def adder(n): return x + n # захват переменной "x" из внешнего контекста return adder
# То же самое, но через безымянные функции: make_adder = lambda x: ( lambda n: x + n )
f = make_adder(10) print f(5) # 15 print f(-1) # 9
Интроспекция и рефлексия
Интроспекция — это способность программы исследовать тип или свойства объекта во время работы программы. Как мы уже упоминали, вы можете поинтересоваться, каков тип объекта, является ли он экземпляром класса. Некоторые языки даже позволяют узнать иерархию наследования объекта. Возможность интроспекции есть в таких языках, как Ruby, Java, PHP, Python, C++ и других. В целом, инстроспекция — это очень простое и очень мощное явление.
В Python самой распространённой формой интроспекции является использование метода dir для вывода списка атрибутов объекта:
Python
class foo(object): def \_\_init\_\_(self, val): self.x = val def bar(self): return self.x
…
dir(foo(5))
=> [‘__class__’, ‘__delattr__’, ‘__dict__’, ‘__doc__’, ‘__getattribute__’, ‘__hash__’, ‘__init__’, ‘__module__’,
‘__new__’, ‘__reduce__’, ‘__reduce_ex__’, ‘__repr__’, ‘__setattr__’, ‘__str__’, ‘__weakref__’, ‘bar’, ‘x’]
Рефлексия — это способность компьютерной программы изучать и модифицировать свою структуру и поведение (значения, мета-данные, свойства и функции) во время выполнения. Простым языком: она позволяет вам вызывать методы объектов, создавать новые объекты, модифицировать их, даже не зная имён интерфейсов, полей, методов во время компиляции. Из-за такой природы рефлексии её труднее реализовать в статически типизированных языках, поскольку ошибки типизации возникают во время компиляции, а не исполнения программы. Тем не менее, она возможна, ведь такие языки, как Java, C# и другие допускают использование как интроспекции, так и рефлексии (но не C++, он позволяет использовать лишь интроспекцию).
По той же причине рефлексию проще реализовать в интерпретируемых языках, поскольку когда функции, объекты и другие структуры данных создаются и вызываются во время работы программы, используется какая-то система распределения памяти. Интерпретируемые языки обычно предоставляют такую систему по умолчанию, а для компилируемых понадобится дополнительный компилятор и интерпретатор, который следит за корректностью рефлексии.
Это очень мощный принцип, который к тому же является обычной практикой в метапрограммировании. Тем не менее, при использовании рефлексии нужно быть очень внимательным. Хотя у неё и есть свои преимущества, код, использующий рефлексию, значительно менее читаем, он затрудняет отладку, а также открывает двери по-настоящему плохим вещами, например, инъекции кода через выражения eval.
Некоторые рефлективные языки предоставляют возможность использования eval-выражений — выражений, которые распознают значение (обычно строку) как выражение. Такие утверждения — это самый мощный принцип рефлексии и даже метапрограммирования, но также и самый опасный, поскольку они представляют собой угрозу безопасности.
Рассмотрим следующий пример кода на Python, который принимает данные из стороннего источника в Сети (это одна из причин, по которой люди пользуются eval-выражениями):
session[‘authenticated’] = False
data = get_data()
foo = eval(data)
Защита программы будет нарушена, если кто-то передаст в метод get_data() такую строку:
“session.update(authenticated=True)”
Для безопасного использования eval-утверждений нужно сильно ограничивать формат входных данных — и обычно это лишь занимает лишнее время.
Компилируемые и интерпретируемые языки
Код может быть исполнен нативно, в операционной системе после конвертации в машинный (путём компиляции) или же исполняться построчно другой программой, которая делает это вместо ОС (интерпретатор).
Компилируемый язык — это такой язык, что программа, будучи скомпилированной, содержит инструкции целевой машины; этот машинный код непонятен людям. Интерпретируемый же язык — это такой, в котором инструкции не исполняются целевой машиной, а считываются и исполняются другой программой (которая обычно написана на языке целевой машины).
Главное преимущество компилируемых языков — это скорость исполнения. Поскольку они конвертируются в машинный код, они работают гораздо быстрее и эффективнее, нежели интерпретируемые, особенно если учесть сложность утверждений некоторых современных скриптовых интерпретируемых языков.
Низкоуровневые языки как правило являются компилируемыми, поскольку эффективность обычно ставится выше кроссплатформенности. Кроме того, компилируемые языки дают разработчику гораздо больше возможностей в плане контроля аппаратного обеспечения, например, управления памятью и использованием процессора. Примерами компилируемых языков являются C, C++, Erlang, Haskell и более современные языки, такие как Rust и Go.
Проблемы компилируемых языков, в общем-то, очевидны. Для запуска программы, написаной на компилируемом языке, её сперва нужно скомпилировать. Это не только лишний шаг, но и значительное усложнение отладки, ведь для тестирования любого изменения программу нужно компилировать заново. Кроме того, компилируемые языки являются платформо-зависимыми, поскольку машинный код зависит от машины, на которой компилируется и исполняется программа.
В отличие от компилируемых языков, интерпретируемым для исполнения программы не нужен машинный код; вместо этого программу построчно исполнят интерпретаторы. Раньше процесс интерпретации занимал очень много времени, но с приходом таких технологий, как JIT-компиляция, разрыв между компилируемыми и интерпретируемыми языками сокращается. Примерами интерпретируемых языков являются PHP, Perl, Ruby и Python. Вот некоторые из концептов, которые стали проще благодаря интерпретируемым языкам:
Независимость от платформы; Рефлексия; Динамическая типизация; Меньший размер исполняемых файлов: Динамические области видимости. Основным недостатком интерпретируемых языком является их невысокая скорость исполнения. Тем не менее, JIT-компиляция позволяет ускорить процесс благодаря переводу часто используемых последовательностей инструкции в машинный код.
Байткод-языки — это такие языки, которые используют для исполнения кода как компиляцию, так и интерпретацию. Java и фреймворк .NET — это типичные примеры байткод-языков. В байткод-языке сперва происходит компиляция программы из человекочитаемого языка в байткод. Байткод — это набор инструкций, созданный для эффективного исполнения интерпретатором и состоящий из компактных числовых кодов, констант и ссылок на память. С этого момента байткод передаётся в виртуальную машину, которая затем интерпретирует код также, как и обычный интерпретатор.
При компиляции кода в байткод происходит задержка, но дальнейшая скорость исполнения значительно возрастает в силу оптимизации байткода. Кроме того, байткод-языки являются платформо-независимыми, превосходя при этом по скорости интерпретируемые. Для них также доступна JIT-компиляция.
компилируемые языки являются самыми эффективными, поскольку они исполняются как машинный код и позволяют использовать аппаратное обеспечение системы. Однако это вводит дополнительные ограничение на написание кода и делает его платформо-зависимым. Интерпретируемые же языки не зависят от платформы и позволяют использовать такие техники динамического программирования, как метапрограммирование. Тем не менее, в скорости исполнения они значительно уступают компилируемым языкам.
Принципы паттернов проектирования
Шаблон проектирования, или паттерн, в разработке программного обеспечения — повторяемая архитектурная конструкция, представляющая собой решение проблемы проектирования, в рамках некоторого часто возникающего контекста.
Будьте осторожны
шаблоны проектирования не являются решением всех ваших проблем;
не пытайтесь использовать их в обязательном порядке — это может привести к негативным последствиям. Шаблоны — это подходы к решению проблем, а не решения для поиска проблем;
если их правильно использовать в нужных местах, то они могут стать спасением, а иначе могут привести к ужасному беспорядку.
Шаблоны бывают следующих трех видов:
Порождающие.
Структурные.
Поведенческие.
Порождающие шаблоны — шаблоны проектирования, которые абстрагируют процесс инстанцирования. Они позволяют сделать систему независимой от способа создания, композиции и представления объектов. Шаблон, порождающий классы, использует наследование, чтобы изменять наследуемый класс, а шаблон, порождающий объекты, делегирует инстанцирование другому объекту.
Существуют следующие порождающие шаблоны:
простая фабрика (Simple Factory); фабричный метод (Factory Method); абстрактная фабрика (Abstract Factory); строитель (Builder); прототип (Prototype); одиночка (Singleton).
Структурные шаблоны в основном связаны с композицией объектов, другими словами, с тем, как сущности могут использовать друг друга. Ещё одним объяснением было бы то, что они помогают ответить на вопрос «Как создать программный компонент?».
Структурные шаблоны — шаблоны проектирования, в которых рассматривается вопрос о том, как из классов и объектов образуются более крупные структуры.
Список структурных шаблонов проектирования: адаптер (Adapter); мост (Bridge); компоновщик (Composite); декоратор (Decorator); фасад (Facade); приспособленец (Flyweight); заместитель (Proxy).
Поведенческие шаблоны связаны с распределением обязанностей между объектами. Их отличие от структурных шаблонов заключается в том, что они не просто описывают структуру, но также описывают шаблоны для передачи сообщений / связи между ними. Или, другими словами, они помогают ответить на вопрос «Как запустить поведение в программном компоненте?»
Поведенческие шаблоны — шаблоны проектирования, определяющие алгоритмы и способы реализации взаимодействия различных объектов и классов.
Поведенческие шаблоны: цепочка обязанностей (Chain of Responsibility); команда (Command); итератор (Iterator); посредник (Mediator); хранитель (Memento); наблюдатель (Observer); посетитель (Visitor); стратегия (Strategy); состояние (State); шаблонный метод (Template Method).
Паттерн Простая фабрика (Simple Factory)
В объектно-ориентированном программировании (ООП), фабрика — это объект для создания других объектов. Формально фабрика — это функция или метод, который возвращает объекты изменяющегося прототипа или класса из некоторого вызова метода, который считается «новым».
Простыми словами: Простая фабрика генерирует экземпляр для клиента, не раскрывая никакой логики.
Когда использовать: Когда создание объекта — это не просто несколько присвоений, а какая-то логика, тогда имеет смысл создать отдельную фабрику вместо повторения одного и того же кода повсюду.
Паттерн Фабричный метод (Fabric Method)
Фабричный метод — порождающий шаблон проектирования, предоставляющий подклассам интерфейс для создания экземпляров некоторого класса. В момент создания наследники могут определить, какой класс создавать. Иными словами, данный шаблон делегирует создание объектов наследникам родительского класса. Это позволяет использовать в коде программы не специфические классы, а манипулировать абстрактными объектами на более высоком уровне.
Простыми словами: Менеджер предоставляет способ делегирования логики создания экземпляра дочерним классам.
Когда использовать: Полезен, когда есть некоторая общая обработка в классе, но необходимый подкласс динамически определяется во время выполнения. Иными словами, когда клиент не знает, какой именно подкласс ему может понадобиться.
Паттерн Абстрактная фабрика (Abstract Factory)
Абстрактная фабрика — порождающий шаблон проектирования, предоставляет интерфейс для создания семейств взаимосвязанных или взаимозависимых объектов, не специфицируя их конкретных классов. Шаблон реализуется созданием абстрактного класса Factory, который представляет собой интерфейс для создания компонентов системы (например, для оконного интерфейса он может создавать окна и кнопки). Затем пишутся классы, реализующие этот интерфейс.
Простыми словами: Фабрика фабрик. Фабрика, которая группирует индивидуальные, но связанные/зависимые фабрики без указания их конкретных классов.
Когда использовать: Когда есть взаимосвязанные зависимости с не очень простой логикой создания.
Паттерн Строитель (Builder)
Строитель — порождающий шаблон проектирования, который предоставляет способ создания составного объекта. Предназначен для решения проблемы антипаттерна «Телескопический конструктор».
Простыми словами: Шаблон позволяет вам создавать различные виды объекта, избегая засорения конструктора. Он полезен, когда может быть несколько видов объекта или когда необходимо множество шагов, связанных с его созданием.
Когда использовать: Когда может быть несколько видов объекта и надо избежать «телескопического конструктора». Главное отличие от «фабрики» — это то, что она используется, когда создание занимает один шаг, а «строитель» применяется при множестве шагов.
Паттерн Прототип (Prototype)
Задаёт виды создаваемых объектов с помощью экземпляра-прототипа и создаёт новые объекты путём копирования этого прототипа. Он позволяет уйти от реализации и позволяет следовать принципу «программирование через интерфейсы». В качестве возвращающего типа указывается интерфейс / абстрактный класс на вершине иерархии, а классы-наследники могут подставить туда наследника, реализующего этот тип.
Простыми словами: Прототип создает объект, основанный на существующем объекте при помощи клонирования.
То есть он позволяет вам создавать копию существующего объекта и модернизировать его согласно вашим нуждам, вместо того, чтобы создавать объект заново.
Когда использовать: Когда необходим объект, похожий на существующий объект, либо когда создание будет дороже клонирования.
Паттерн Одиночка (Singleton)
Одиночка — порождающий шаблон проектирования, гарантирующий, что в однопроцессном приложении будет единственный экземпляр некоторого класса, и предоставляющий глобальную точку доступа к этому экземпляру.
Простыми словами: Обеспечивает тот факт, что создаваемый объект является единственным объектом своего класса.
Вообще шаблон одиночка признан антипаттерном, необходимо избегать его чрезмерного использования. Он необязательно плох и может иметь полезные применения, но использовать его надо с осторожностью, потому что он вводит глобальное состояние в ваше приложение и его изменение в одном месте может повлиять на другие части приложения, что вызовет трудности при отладке. Другой минус — это то, что он делает ваш код связанным.