Skip to content

Instantly share code, notes, and snippets.

@marcy-terui
Last active August 29, 2015 14:07
Show Gist options
  • Select an option

  • Save marcy-terui/caf5dee53a62ab4ccd14 to your computer and use it in GitHub Desktop.

Select an option

Save marcy-terui/caf5dee53a62ab4ccd14 to your computer and use it in GitHub Desktop.
isucon4予選AMIが公開されたのでPHPでやってみた(score:54491)
<?php
require_once 'limonade/lib/limonade.php';
function configure() {
option('base_uri', '/');
option('session', 'isu4_qualifier_session');
$host = getenv('ISU4_DB_HOST') ?: 'localhost';
$port = getenv('ISU4_DB_PORT') ?: 3306;
$dbname = getenv('ISU4_DB_NAME') ?: 'isu4_qualifier';
$username = getenv('ISU4_DB_USER') ?: 'root';
$password = getenv('ISU4_DB_PASSWORD');
$db = null;
try {
$db = new PDO(
'mysql:host=' . $host . ';port=' . $port. ';dbname=' . $dbname,
$username,
$password,
[ PDO::ATTR_PERSISTENT => true,
PDO::MYSQL_ATTR_INIT_COMMAND => 'SET CHARACTER SET `utf8`',
]
);
} catch (PDOException $e) {
halt("Connection faild: $e");
}
$db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
option('db_conn', $db);
$config = [
'user_lock_threshold' => getenv('ISU4_USER_LOCK_THRESHOLD') ?: 3,
'ip_ban_threshold' => getenv('ISU4_IP_BAN_THRESHOLD') ?: 10
];
option('config', $config);
}
function uri_for($path) {
$host = $_SERVER['HTTP_X_FORWARDED_HOST'] ?: $_SERVER['HTTP_HOST'];
return 'http://' . $host . $path;
}
function get($key) {
return set($key);
}
function before() {
layout('base.html.php');
}
function calculate_password_hash($password, $salt) {
return hash('sha256', $password . ':' . $salt);
}
function login_log($succeeded, $login, $user_id=null, $ip=null, $created_at=null, $insert = true) {
if (is_null($ip)) $ip = $_SERVER['REMOTE_ADDR'];
if (is_null($created_at)) $created_at = date('Y-m-d H:i:s');
$log = array('created_at' => $created_at, 'user_id' => $user_id, 'login' => $login, 'ip' => $ip, 'succeeded' => $succeeded);
$db = option('db_conn');
if ($insert) {
$stmt = $db->prepare('INSERT DELAYED INTO login_log (`created_at`, `user_id`, `login`, `ip`, `succeeded`) VALUES (:created_at,:user_id,:login,:ip,:succeeded)');
$stmt->bindValue(':created_at', $log['created_at']);
$stmt->bindValue(':user_id', $log['user_id']);
$stmt->bindValue(':login', $log['login']);
$stmt->bindValue(':ip', $log['ip']);
$stmt->bindValue(':succeeded', $log['succeeded'] ? 1 : 0);
$stmt->execute();
}
$ip_key = 'fail_ip_' . $ip;
$user_key = 'fail_user_' . $user_id;
if ($succeeded) {
apc_store($ip_key, 0);
if (!is_null($user_id)) {
apc_store($user_key, 0);
if (apc_exists("now_login_" . $user_id)) {
apc_store("last_login_" . $user_id, apc_fetch("now_login_" . $user_id));
}
apc_store("now_login_" . $user_id, $log);
}
} else {
if (apc_exists($ip_key)) {
apc_inc($ip_key);
} else {
apc_store($ip_key, 1);
}
if (!is_null($user_id)) {
if (apc_exists($user_key)) {
apc_inc($user_key);
} else {
apc_store($user_key, 1);
}
}
}
}
function user_locked($user) {
if (empty($user)) { return null; }
$config = option('config');
$user_key = 'fail_user_' . $user['id'];
return apc_fetch($user_key) >= $config['user_lock_threshold'];
}
# FIXME
function ip_banned() {
$config = option('config');
$ip_key = 'fail_ip_' . $_SERVER['REMOTE_ADDR'];
return apc_fetch($ip_key) >= $config['ip_ban_threshold'];
}
function attempt_login($login, $password) {
$apc_key = "login_" . $login;
$user = apc_fetch($apc_key);
if (ip_banned()) {
login_log(false, $login, isset($user['id']) ? $user['id'] : null);
return ['error' => 'banned'];
}
if (user_locked($user)) {
login_log(false, $login, $user['id']);
return ['error' => 'locked'];
}
if (!empty($user) && calculate_password_hash($password, $user['salt']) == $user['password_hash']) {
login_log(true, $login, $user['id']);
return ['user' => $user];
}
elseif (!empty($user)) {
login_log(false, $login, $user['id']);
return ['error' => 'wrong_password'];
}
else {
login_log(false, $login);
return ['error' => 'wrong_login'];
}
}
function current_user() {
if (empty($_SESSION['user_id'])) {
return null;
}
$apc_key = "user_" . $_SESSION['user_id'];
if (!apc_exists($apc_key)) {
unset($_SESSION['user_id']);
return null;
} else {
$user = apc_fetch($apc_key);
}
return $user;
}
function last_login() {
$user = current_user();
if (empty($user)) {
return null;
}
return apc_fetch("last_login_" . $user['id']);
}
function banned_ips() {
$db = option('db_conn');
$threshold = option('config')['ip_ban_threshold'];
$ips = [];
$stmt = $db->prepare('SELECT ip FROM (SELECT ip, MAX(succeeded) as max_succeeded, COUNT(1) as cnt FROM login_log GROUP BY ip) AS t0 WHERE t0.max_succeeded = 0 AND t0.cnt >= :threshold');
$stmt->bindValue(':threshold', $threshold);
$stmt->execute();
$not_succeeded = $stmt->fetchAll(PDO::FETCH_COLUMN, 0);
$ips = array_merge($not_succeeded);
$stmt = $db->prepare('SELECT ip, MAX(id) AS last_login_id FROM login_log WHERE succeeded = 1 GROUP by ip');
$stmt->execute();
$last_succeeds = $stmt->fetchAll();
foreach ($last_succeeds as $row) {
$stmt = $db->prepare('SELECT COUNT(1) AS cnt FROM login_log WHERE ip = :ip AND :id < id');
$stmt->bindValue(':ip', $row['ip']);
$stmt->bindValue(':id', $row['last_login_id']);
$stmt->execute();
$count = $stmt->fetch(PDO::FETCH_ASSOC)['cnt'];
if ($threshold <= $count) {
array_push($ips, $row['ip']);
}
}
// apc_clear_cache("user");
return $ips;
}
function locked_users() {
$threshold = option('config')['user_lock_threshold'];
$user_ids = [];
$db = option('db_conn');
$stmt = $db->prepare('SELECT login FROM (SELECT user_id, login, MAX(succeeded) as max_succeeded, COUNT(1) as cnt FROM login_log GROUP BY user_id) AS t0 WHERE t0.user_id IS NOT NULL AND t0.max_succeeded = 0 AND t0.cnt >= :threshold');
$stmt->bindValue(':threshold', $threshold);
$stmt->execute();
$not_succeeded = $stmt->fetchAll(PDO::FETCH_COLUMN, 0);
$user_ids = array_merge($not_succeeded);
$stmt = $db->prepare('SELECT user_id, login, MAX(id) AS last_login_id FROM login_log WHERE user_id IS NOT NULL AND succeeded = 1 GROUP BY user_id');
$stmt->execute();
$last_succeeds = $stmt->fetchAll();
foreach ($last_succeeds as $row) {
$stmt = $db->prepare('SELECT COUNT(1) AS cnt FROM login_log WHERE user_id = :user_id AND :id < id');
$stmt->bindValue(':user_id', $row['user_id']);
$stmt->bindValue(':id', $row['last_login_id']);
$stmt->execute();
$count = $stmt->fetch(PDO::FETCH_ASSOC)['cnt'];
if ($threshold <= $count) {
array_push($user_ids, $row['login']);
}
}
return $user_ids;
}
dispatch_get('/', function() {
return html('index.html.php');
});
dispatch_post('/login', function() {
$result = attempt_login($_POST['login'], $_POST['password']);
if (!empty($result['user'])) {
session_regenerate_id(true);
$_SESSION['user_id'] = $result['user']['id'];
return redirect_to('/mypage');
}
else {
switch($result['error']) {
case 'locked':
flash('notice', 'This account is locked.');
break;
case 'banned':
flash('notice', "You're banned.");
break;
default:
flash('notice', 'Wrong username or password');
break;
}
//Dirty hack...
return header('Location: /?' . $result['error']);
}
});
dispatch_get('/mypage', function() {
$user = current_user();
if (empty($user)) {
flash('notice', 'You must be logged in');
//Dirty hack...
return header('Location: /?nologin');
}
else {
set('user', $user);
set('last_login', last_login());
return html('mypage.html.php');
}
});
dispatch_get('/report', function() {
return json_encode([
'banned_ips' => banned_ips(),
'locked_users' => locked_users()
]);
});
dispatch_get('/init', function() {
apc_clear_cache("user");
$db = option('db_conn');
$stmt = $db->prepare('SELECT * FROM users');
$stmt->execute();
while ($user = $stmt->fetch(PDO::FETCH_ASSOC)) {
apc_store("login_" . $user['login'], $user);
apc_store("user_" . $user['id'], $user);
}
$stmt = $db->prepare('SELECT * FROM login_log');
$stmt->execute();
while ($log = $stmt->fetch(PDO::FETCH_ASSOC)) {
login_log((bool)$log['succeeded'], $log['login'], $log['user_id'], $log['ip'], $log['created_at'], false);
}
});
run();
[mysqld]
datadir=/var/lib/mysql
socket=/var/lib/mysql/mysql.sock
symbolic-links=0
max_allowed_packet=300M
skip-name-resolve
thread-cache-size = 10
sort-buffer-size = 256K
read-buffer-size = 256K
read-rnd-buffer-size = 256K
join-buffer-size = 256K
tmp-table-size = 64M
max-heap-table-size = 64M
innodb-buffer-pool-size = 1024M
innodb-log-file-size = 16M
innodb-log-buffer-size = 8M
#slow-query-log = 1
#slow-query-log-file = /tmp/mysql_slow.log
#log_queries_not_using_indexes
#innodb_buffer_pool_dump_at_shutdown = 1
#innodb_buffer_pool_dump_now = 1
#innodb_buffer_pool_load_at_startup = 1
#innodb_buffer_pool_load_now = 1
performance_schema = 0
[mysqld_safe]
log-error=/var/log/mysqld.log
pid-file=/var/run/mysqld/mysqld.pid
worker_processes 4;
worker_rlimit_nofile 20000;
pcre_jit on;
events {
worker_connections 4096;
use epoll;
}
http {
include /etc/nginx/mime.types;
sendfile on;
tcp_nopush on;
tcp_nodelay on;
send_timeout 10;
keepalive_timeout 5 3;
keepalive_requests 30;
output_buffers 1 64k;
etag off;
client_header_timeout 5;
client_body_timeout 5;
client_body_temp_path /dev/shm/client_temp 1 2 3;
client_max_body_size 10m;
client_body_buffer_size 32k;
client_header_buffer_size 2k;
large_client_header_buffers 4 8k;
server_tokens off;
access_log off;
upstream php-fpm {
server unix:/dev/shm/php-fpm.sock;
}
server {
location ~ ^/(images|stylesheets) {
root /dev/shm/public;
break;
}
location = / {
root /dev/shm/public;
if ($args ~ 'locked') {
rewrite ^(.*)$ /index_locked.html break;
}
if ($args ~ 'banned') {
rewrite ^(.*)$ /index_banned.html break;
}
if ($args ~ 'wrong') {
rewrite ^(.*)$ /index_wrong.html break;
}
if ($args ~ 'nologin') {
rewrite ^(.*)$ /index_nologin.html break;
}
rewrite ^(.*)$ /index.html break;
break;
}
location / {
root /home/isucon/webapp/php/src;
fastcgi_pass php-fpm;
fastcgi_index index.php;
fastcgi_read_timeout 120;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_param QUERY_STRING $query_string;
fastcgi_param REQUEST_METHOD $request_method;
fastcgi_param CONTENT_TYPE $content_type;
fastcgi_param CONTENT_LENGTH $content_length;
fastcgi_param SCRIPT_NAME $fastcgi_script_name;
fastcgi_param REQUEST_URI $request_uri;
fastcgi_param DOCUMENT_URI $document_uri;
fastcgi_param DOCUMENT_ROOT $document_root;
fastcgi_param SERVER_PROTOCOL $server_protocol;
fastcgi_param HTTPS $https if_not_empty;
fastcgi_param GATEWAY_INTERFACE CGI/1.1;
fastcgi_param SERVER_SOFTWARE nginx/$nginx_version;
fastcgi_param REMOTE_ADDR $http_x_forwarded_for;
fastcgi_param REMOTE_PORT $remote_port;
fastcgi_param SERVER_ADDR $server_addr;
fastcgi_param SERVER_PORT $server_port;
fastcgi_param SERVER_NAME $server_name;
fastcgi_param REDIRECT_STATUS 200;
rewrite ^(.*)$ /index.php?$1 break;
}
}
}
[www]
;user = isucon
;group = isucon
listen = /dev/shm/php-fpm.sock
listen.owner = isucon
listen.group = isucon
listen.mode = 0666
;listen = 0.0.0.0:8080
pm = static
pm.max_children = 4
pm.start_servers = 4
pm.min_spare_servers = 4
pm.max_spare_servers = 4
pm.process_idle_timeout = 0
pm.max_requests = 0
pm.status_path = /status
ping.path = /ping
zend_extension=/home/isucon/.local/php/lib/php/extensions/no-debug-non-zts-20131226/opcache.so
opcache.memory_consumption=128
opcache.interned_strings_buffer=8
opcache.max_accelerated_files=4000
opcache.revalidate_freq=0
opcache.fast_shutdown=1
opcache.enable_cli=1
opcache.validate_timestamps=0
opcache.save_comments=0
opcache.load_comments=0
session.save_handler=files
session.save_path=/dev/shm/
extension=/home/isucon/.local/php/lib/php/extensions/no-debug-non-zts-20131226/apcu.so
apc.shm_size=1024M
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment