Last active
August 29, 2015 14:07
-
-
Save marcy-terui/caf5dee53a62ab4ccd14 to your computer and use it in GitHub Desktop.
isucon4予選AMIが公開されたのでPHPでやってみた(score:54491)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <?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(); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| [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 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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; | |
| } | |
| } | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| [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 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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