Tag Archives: php

nginx+php-fpm под Windows

Здравствуйте!

nginx

Apache – король веб-серверов, если можно так сказать. Но на пятки ему наступает даже не IIS от Microsoft, не lighttpd, а nginx (произносится как Энджин-Икс, engine с английского мотор, двигатель) нашего соотечественника Сысоева.

Чем он хорош? Говорят, что статика отдаётся гораздо быстрее, чем у Апача, да и динамика я думаю тоже. Он жрёт меньше ресурсов, что может быть критически важно для нагруженных серверов. Раньше мнгоие применял связку nginx+Apache – nginx для отдачи статики (рисунков, js/css etc.), а Апач – для отдачи динамики (PHP/Perl/Python/Ruby etc.). Но теперь nginx можно применять без Апача, так как для него появилось куча плагинов и дополнений, поэтому вместо связки nginx+Apache+PHP (мы тут говорим о PHP-среде) легко настроить просто nginx+php-fpm. Ладно, об нём написано куча литературы, не буду повторяться, опишу лишь процесс установки nginx+php-fpm под Виндовс (Windows).

Хотя, конечно, nginx органичней всего чувствует себя в FreeBSD и Linux (любой Unix-среде, наверное), под Винду он тоже неплохо работает, по крайней мере я его у себя на домашнем компе установил, чтобы тестировать некоторые штуки.

Итак, процесс установки/первичной настройки. Этот процесс расписан здесь: http://nginx.org/ru/docs/windows.html
я приведу лишь выжимку.

Смотрим доступные версии nginx под windows здесь: http://nginx.org/en/download.html
Сейчас есть версия 1.8.0, несколько месяцев назад я устанавливал 1.6.2, которая и сейчас у меня работает.
Итак, скачиваем текущую версию под windows: http://nginx.org/download/nginx-1.8.0.zip

Для удобства примем то, что я пользую сейчас:
Создаём папку C:\usr. Заходим в неё и распаковываем nginx-1.8.0.zip здесь (это можно проделать через GUI-интерфэйс).
Затем запускам териминал и заходим:

> C:
> cd C:\usr\nginx-1.8.0
> start nginx

Тут Виньда может выкинуть окошко с предупреждением (см. скриншот), что nginx пытается получить доступ в сеть. Мы конечно же разрешаем.

allow nginx to network

Проверяем, запущен ли nginx и видим результат:

>tasklist /fi "imagename eq nginx.exe"
 
Image Name                     PID Session Name        Session#    Mem Usage
========================= ======== ================ =========== ============
nginx.exe                     1336 Console                    1      6,440 K
nginx.exe                     3136 Console                    1      6,268 K
nginx.exe                     4864 Console                    1      6,496 K
nginx.exe                     6544 Console                    1      5,872 K

Остановим nginx нормально: nginx -s quit. Есть ещё несколько полезных команд для nginx:
nginx -s stop – останов nginx в любом случае (применяется, если nginx -s quit не сработает).
nginx -s reload – перезагрузка .conf файлов (конфигурации)
nginx -s reopen – переоткрытие .log файлов (полезна, если мы удалили или переместили логи при работающем nginx).

Итак, мы остановили nginx сейчас, так как прежде чем его запускать, надо правильно настроить .conf файлы. Они расположены в папке conf. Стандартный файл настройки – nginx.conf, из него директивой include могут подсоединяться другие файлы из этой (впрочем, и из любой другой) папки.
Например, директива include mime.types; в секции http присоединит файл mime.types, в котором находится определения всех стандартных MIME-типов. Впрочем, сам конфиг я обсуждать здесь не буду, о нем много написано в инете, приведу лишь пример своего конфига с краткими пояснениями.

Предупреждение: это конфиг для моей домашней тестовой среды. Для рабочего сервера требуется более тонкая настройка.

worker_processes  1;

error_log  logs/error.log;
#error_log  logs/error.log  notice;
#error_log  logs/error.log  info;

events {
    worker_connections  1024;
}


http {
    include       mime.types;
    default_type  application/octet-stream;
    
    #
    # Формат лога делаем как у Апача
    #
    log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
                      '$status $body_bytes_sent "$http_referer" '
                      '"$http_user_agent" "$http_x_forwarded_for"';
    access_log  logs/access.log  main;

    # sendfile        on;
    #tcp_nopush     on;
  
    keepalive_timeout  65;

    # Сжатие gzip на лету
    gzip  on;

    server {
        # listen       801;
        server_name  localhost;
				autoindex on; # allow dir listing
				root E:/sites;
				
        #charset koi8-r;
        #access_log  logs/host.access.log  main;

	#
        # запретим доступ ко всем файлам, начинающимся с точки 
	#
        location ~ /\. {
            deny  all;
        }

        location / {
            root   E:/sites;
            index  index.html index.htm index.php;
        }

        #error_page  404              /404.html;

        # redirect server error pages to the static page /50x.html
        #
        error_page   500 502 503 504  /50x.html;
        location = /50x.html {
            root   html;
        }

	#
        # передаем все PHP скрипты серверу FastCGI, слушающему на 127.0.0.1:9123
        #
        location ~ \.php$ {
            root           E:/sites;
            fastcgi_pass   127.0.0.1:9123;
            fastcgi_index  index.php;
            fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
            include        fastcgi_params;
        }
    }
}

Итак, в этом конфиге большинство настроек оставлено по умолчанию, а корень сайтов у нас в E:\sites, что в первую очередь делает команда root E:/sites. Обратите внимание на прямые слэши в стиле Unix в пути к папкам и файлам – это требование nginx, даже для Windows-версии.

Теперь можно запускать nginx (start nginx), если мы его останавливали перед этим, либо применить команду nginx -s reload, чтобы сервер перечитал конфиги без остановки своей работы, что полезно при работающем внешнем сайте.

Итак, теперь надо настроить PHP-FPM для Windows. Учтите, что мы уже в нашем конфиге сделали его поддержку на порту 9123 (под-секция location ~ \.php$)

PHP-FPM для Windows

1. Скачиваем свежий (или версию по выбору) .zip-архивчик с http://windows.php.net/download/. Архивчик должен быть VC11/VC9, что содержит в себе FastCGI-файл (phpcgi.exe).
2. Создаем папку в C:\usr, например с именем php-5.6.9 и распаковываем в неё содержимое архива.
3. Редактируем файл php.ini в соотв. со своими предпочтениями, единственное, убедиться, что у нас есть такая строка:

# nginx security setting
cgi.fix_pathinfo=0

Она закрывает одну из старых уязвимостей nginx. Далее можно подключить PHP-модули по вкусу, расскоментировав их в соотв. секции и произвести другие настройки.

4. Теперь создадим .bat-файл, например php-fpm-start.bat с таким содержимым:

@echo off
echo Starting PHP FastCGI...
set path=c:\usr\php-5.6.9;%PATH%
C:\usr\php-5.6.9\php-cgi.exe -b 127.0.0.1:9123 -c C:\usr\php-5.6.9\php.ini

и запустим его. Если мы его запускаем из GUI, то появится окно консоли и останется открытым, придётся с этим смириться.

Всё, наш сервер мы уже давно настроили на соединение с этим PHP процессом.

Для проверки создаём файл index.php в папке E:\sites с таким содержимым:

<?php
phpinfo();

Теперь направляем наш любимый браузер на http://localhost и видим такую примерно картину:

phpinfo() начало

phpinfo

phpinfo() с версией nginx

phpinfo nginx

Здесь же можно посмортеь переменные среды и подключаемые модули. Для совместимости nginx создет переменные среды, совместимые с апачевскими, например _SERVER[“SERVER_NAME”], _SERVER[“DOCUMENT_ROOT”], _SERVER[“REQUEST_URI”], _SERVER[“SCRIPT_NAME”] и т.д., и мы можем использовать их в своих PHP-сценариях, как делали это в случае с Апачем.

До свидания!

PHP: Групповая обработка файлов

Давече встала передо мной проблема – перевести весь сайт на UTF-8 с Windows-1251. Для этого нам надо (помимо возможно нового парсинга значений из БД) просто перекодировать все страницы сайта.
Вручную я за десять секунд могу перекодировать текстовый файл в Notepad++, но если таких файлов десятки или сотни, что нормально для стандартного сайта, это может встать геморроем.

Конечно, тут нужна групповая обработка файлов. Что делать? Юзать утилиту iconv – но она опять таки предназанчена для конвертации одного файла, а для групповой обработки надо изощряться через всякий find’ы, пайпы и т.п. Я не настолько пока силён в Unix-командах, чтобы быстро и без труда сделать всё как надо.

Для Windows нашлось (опять таки при поверхностном гуглении, так как времени для долгого изыскания нету) пара утилит, но они все какие-то корявые оказались и тупые.

Что делать? Писать самому! Тем более, что у нас под рукой есть такой простой и мощный язык как PHP Hypertext Preprocessor.

Первым делом нам надо написать универсальную функцию, которая обходит дерево каталогов и выбирает имена файлов для обработки.

Сразу определим параметры этой функции –

function processFiles($dir, $callback, $mask = '/.*/', $recursive = true)
{ ... }

Параметры:
$dir – имя каталога для начала обхода.

$callback – функция, которая будет вызвана с именем файла в качестве единственного параметра. Решим, что при вызове этой функции мы будем устанавливать рабочий каталог тот, где находится данный файл, чтобы функция могла лекго читать и писать данные, сообразуясь с этим.

$mask – PCRE – маска файлов. Функция $callback будет вызываться только для тех файлов, для которых preg_match($mask, полное
имя файла)
функция вернёт true.
Благодаря этому параметру мы можем отобрать только те файлы, которые нам действительно нужны, а не перекладывать эту функцию на вызываемую функцию (блин, что за каламбур получился? ладно, пусть будет).
По умолчанию (это не обязательный параметр) мы выбираем все файлы, кроме скрытых (то есть те, имя которых начинается с точки – по Unix соглашению).

$recursive – также необязательный параметр (по умолчанию true) – вызывать ли рекурсивно обработку файлов в подпапках текущей папки.
Обычно нам так и нужно, поэтому дефолтное значение – да, вызывать.

Вот полной код этой функции обхода дерева каталогов на PHP:

// рекурсивный (по умолчанию) обход папок
// recursive walking directories
function processFiles($dir, $callback, $mask = '/.*/', $recursive = true)
{
	$dh = opendir($dir);
	if(!$dh)
		return;
 
	$curDir = getcwd();
	chdir($dir);
 
	while (($entry = readdir($dh)) !== false) {
		if(substr($entry,0,1) == '.')
		// hidden entry
			continue;
		$fullName = $dir.'/'.$entry;
		if(is_dir($entry)) {
			if ($recursive)
				processFiles($entry, $callback, $mask);
			continue;
		}
		if(preg_match($mask,$fullName))
			call_user_func($callback,$entry);
	}
 
	chdir($curDir);
}

В этом куске кода нет ничего сложного, все PHP-функции довольно стары и стандартны. Можно отметить только, что пропуская скрытые файлы – hidden entry (по Unix соглашению – то есть файлы и папки, начинающиеся с точки), мы параллельно пропускаем `.` и `..` вхождения, которые также включаются функцией readdir в листинг файлов.

Теперь осталось дело за малым – написать callback-функцию для обработки файлов.

Предупреждение! Перед обработкой файлов всегда делайте резервную копию этих файлов, чтобы в случае если что пойдёт не так, вы всегда смогли безболезненно откатиться назад.

Самое простое что приходит на ум – это вывод полного имени файла, чтобы убедиться, что отбираются именно нужные нам файлы, которые можно безболезненно обрабатывать.

Вот как может выглядеть такая функция:

// просто выведем полное имя файла (с текущим путём)
function listFiles($name)
{
    echo 'File: '.getcwd().'/'.$name.' ('.file_exists($name).')<br>';
}

Теперь попробуем применить наши новые две функции с маской для файлов – выберем только текстовые файлы с расширениями php,html,js,css или всех тех, что находятся в папке doc (и её подпапках):

processFiles('.', 'listFiles', '#\.php$|\.html$|\.js$|\.css$|doc/#');

Изучив в браузере (я запускаю свои скрипты через браузер для текущего сервера, но вы можете конечно использовать CLI, для этого только в выводе вместо ‘<br>’ нужно использовать “\n”) вывод и убедившись, что отбираются именно нужные нам файлы, можно приступить к конечной цели нашей задачи – переконвертация файлов из кодировки Windows-1251 в кодировку UTF-8.

На PHP такая функция может занимать от одной до трёх строчек. Чтобы было понятнее (я люблю ясность во всём, но не в ущерб краткости иногда), сделаем сначала такую функцию из трёх строчек:

function reEncode($name)
{
// конвертируем файл из Windows-1251 в UTF-8
	$in = file_get_contents($name);
	$out = iconv('windows-1251','utf-8',$in);
	file_put_contents($name, $out);
}

Благодаря мощной PHP-функции iconv конвертация не вызывает никаких проблем, если мы знаем названия кодировок, с которыми работаем, и iconv тоже их знает.

А вот, чтобы не быть голословным, тоже самое, но одной строчкой:

file_put_contents($name, iconv('windows-1251','utf-8',file_get_contents($name)));

Я лично предпочитаю использовать первый вариант, так как он не намного длиннее, но на порядок понятнее. Впрочем, это, наверное, дело вкуса.

Итак, наша задача успешно решена – вызываем обработку наших файлов примерно так:

processFiles('.', 'reEncode', '#\.php$|\.html$|\.js$|\.css$|doc/#');

Запускаем её для текущего каталога, и через какое-то время (зависит от кол-во файлов и папок) все наши текстовые файлы стали UTF-8!

Красота! Но на этом мы не останавливаемся. Нашу замечательную функцию processFiles мы можем использовать в самых разнообразных целях, какие только нам нужны.

Например, конвертим все наши документы в Unix-EOL (\n вместо \r\n на концах строк):

function toUnixEOL($name)
{
// конвертим концы строк из стандарта Windows (\r\n) в стандарт Unix (\n)
	$in = file_get_contents($name);
	$out = str_replace("\r\n","\n",$in);
	file_put_contents($name, $out);
}

Так же мы можем запустить групповую обработку для изображений – создадим для всех наших изображений миниатюры размером к примеру 150*150 точек в формате PNG черно-белого варианта (см. ниже).

Благодря PHP эта задача кажется легче чем думается, и нам не нужны никакие сторонние утилиты, платные или бесплатные – мы всё можем сделать своими руками, точнее своим мозгом, а что может быть приятнее?

Итак, вызов инструкций:

// полный путь для папки с миниатюрами (thumbs)
$targetDir = getcwd().'/inc/thumbs';
// если папка не существует, создадим её
if(!@opendir($targetDir))
	mkdir($targetDir);
processFiles('./inc/img', 'processImage', '/\.jpg$|\.jpeg$|\.png$|\.gif$/');

сделает из изображений в папке ./inc/img миниатюры 150*150 пикселей и поместит их в папку ./inc/thumbs

А вот как может выглядеть функция processImage:

/* ***********************************************
	Обработка изображений
	Вход -
		$name - имя изображения в текущей папке
********************************************** */
function processImage($name) {
// уменьшаем картинку до 150*150 пикселей,
// преобразуем в чёрно-белое 
// и сохраняем как PNG в отдельной папке
	global $targetDir;
	// имя для нового изображения
	$pathInfo = pathinfo($name);
	$fileName = $targetDir.'/'.$pathInfo['filename'].'.png';
 
        // распознаём только JPEG (JPG), PNG и GIF
	$him = @imagecreatefromjpeg($name);
	if(!$him) {
		$him = @imagecreatefromgif($name);
		if(!$him) {
			$him = @imagecreatefrompng($name);// может это PNG?
			if(!$him)
				return false;
		}
	}
	// удалим файл, если есть случайно уже
	if(file_exists($fileName))
            unlink($fileName);
 
	resizeImage($him,150,$fileName);	// сохраняем оригинально изображение
	// чистим память (поможем PHP)
	imagedestroy($him);
	unset($him);
 
	return true;
}
 
/*
	преобразуем изображение -
	изменим размеры до 150*150 пикселей,
	превратим в черно-белое и сохраним в формате PNG
 
	Вход:
		$originalImage - GD-дескриптор оригинального изображения
		$newWidth - новая ширина (и высота в нашем случае)
		$fileName - путь для сохранения обработанного изображения
*/
function resizeImage($originalImage,$newWidth,$fileName){
    // получим оригинальные размеры
    $width  = imagesx($originalImage);
    $height = imagesy($originalImage);
 
    // для сохранения ASPECT RATIO можно воспользоваться
    // след. закомментированной формулой
    // $newHeight = round(($height * $newWidth) / $width);
 
    // но мы делаем высоту изображения равной его ширине
    $newHeight = $newWidth;	// make it square
 
    // resize the original image
    $newImage = imagecreatetruecolor($newWidth, $newHeight);
    imagecopyresampled($newImage, $originalImage, 0, 0, 0, 0, 
                          $newWidth, $newHeight, $width, $height);
    // преобразуем в черно-белое (зачем? - просто для примера)
    imagefilter($newImage, IMG_FILTER_GRAYSCALE);
    // сохраняем новое изображение
    imagepng($newImage, $fileName);
    imagedestroy($newImage);
    unset($newImage);
}

Что ещё можно делать с файлами в групповой обработке? Переименовывать! Это уже задача для 1-го класса Школы PHP в Хогвардсе!

Удачи и счастливого кодинга.