Tag Archive api

Laravel: Basic API Token Authentication อย่างง่าย

ทำระบบ api ส่งข้อมูลให้ฝั่ง mobile ดึงข้อมูล บางส่วนมันไม่ใช่ความลับ จะทำระบบ authentication โหดๆ ก็เปลือง จะไม่ตรวจอะไรเลยก็ดูมันอ่อนไป สุดท้ายมาจบที่คำว่า basic API token คือ ขอแค่ส่ง token มาให้ถูกก็พอละจะได้ไม่โหลด server มาก ไว้ใช้กับส่วนที่เป็นการดึงข้อมูล common ๆ ง่ายๆ ที่ user ใช้ร่วมกันได้ไม่มีความลับอะไร

เริ่มจากทำ middleware ชื่อ apitoken ง่ายๆ มาใช้เอง

  1. ใช้ command สร้างขึ้นมา
    php artisan make:middleware ApiToken

    มันจะสร้างไฟล์ขึ้นมาใหม่ที่ \app\Http\Middleware

    <?php
    
    namespace App\Http\Middleware;
    
    use Closure;
    
    class ApiToken
    {
        /**
         * Handle an incoming request.
         *
         * @param  \Illuminate\Http\Request  $request
         * @param  \Closure  $next
         * @return mixed
         */
        public function handle($request, Closure $next)
        {
            return $next($request);
        }
    }
  2. แก้โดยให้อ่านค่ามาจาก Environment Configuration แล้วเทียบกับค่าที่ส่งมา ว่าตรงมั๋ย ตรงก็ปล่อยผ่อนไป ไม่ตรงก็อด
    <?php
    
    namespace App\Http\Middleware;
    
    use Closure;
    
    class ApiToken
    {
        /**
         * Handle an incoming request.
         *
         * @param  \Illuminate\Http\Request  $request
         * @param  \Closure  $next
         * @return mixed
         */
        public function handle($request, Closure $next)
        {
    
            if ($request->expectsJson()) {
    
                if (env('API_TOKEN') != $request->header('token')) {
                    return response('Unauthorized.', 401);
                }
    
            } else {
                return route('login');
            }
    
            return $next($request);
        }
    }
    

    เพิ่มขึ้นมาไม่กี่บรรทัดเอง

  3. เปิดไฟล์ .env มาเพิ่ม config token ที่ใช้เข้าไป
    ...
    API_TOKEN=a6b042ae282d91ab2c25bc7ae515af68
    ...
  4. กำหนด url ที่ใช้ basic token ง่ายๆของเราโดยการแก้ routing ตามแบบ
    Route::group([
        'middleware' => 'api.token',
        'prefix' => 'common',
    ], function () {
    
        Route::get('cars', 'CarsController@json');
    
    });

    ทุก route ที่อยู่ใน block นี้จะได้รับการปกป้องด้วย api.token

  5. แต่ตอนนี้ laravel ยังไม่รู้จักกับ api.token เราจะต้องไปเปิดไฟล์ \app\Http\Kernel.php และเพิ่ม $routeMiddleware ตามตัวอย่าง
        protected $routeMiddleware = [
    ...
            'api.token' => \App\Http\Middleware\ApiToken::class,
    ...
        ];

เทสโดยใช้ Postman เรียก url ตามเดิม แต่จะมี common เพิ่มเข้ามาเพราะเพิ่มไปใน route เช่น http://localhost/prototype_laravel/public/api/cars แก้เป็น http://localhost/prototype_laravel/public/api/common/cars และต้องส่ง header ไปโดยมี

  • Accept application/json
  • token a6b042ae282d91ab2c25bc7ae515af68c

มาพร้อมๆ กันถึงจะสามารถ call api ตัวนี้ได้สำเร็จ ไม่งั้นจะเจอกับ Unauthorized.

google maps: HTML5.Geolocation

คุณสมบัติใหม่ของ html5 คือ Geolocation API ช่วยให้ browser สามารถส่งตำแหน่งของตัวมันเองกลับไปให้ server ได้โดยใช้ตำแหน่ง gps หรือจากหมายเลข ip

แต่เพราะมันกระทบถึงความเป็นส่วนตัว ในการเขียนจึงต้องเผื่อว่า user ไม่อนุญาตให้ส่งตำแหน่งของตัวเองกลับมาไว้ด้วย ในตัวอย่างใช้

/* get position from HTML5 Geolocation */
function getGeolocation() {
    var position = false;
    if (navigator.geolocation) {
        navigator.geolocation.getCurrentPosition(function(successCallback, errorCallback) {

            position = successCallback.coords;

            document.getElementById('latitudeA').innerHTML = successCallback.coords.latitude;
            document.getElementById('longitudeA').innerHTML = successCallback.coords.longitude;

        }, function() {
            handleLocationError(true, infoWindow, map.getCenter());
        });
    } else {
        /*Browser doesn't support Geolocation*/
        handleLocationError(false, infoWindow, map.getCenter());

        return false;
    }

    return position;
}

ส่ง position กลับมา ให้

function initMap(defaultLatitude = 13.7651991, defaultLongitude = 100.5368694) {
    position = getGeolocation();

    if (position == false || position == null || position === undefined || position.length <= 0 || typeof position == 'undefined') {
        position = {
            "latitude": defaultLatitude,
            "longitude": defaultLongitude,
        };
    }

    createMap('mapA', position);
}

แต่ถ้าหากไม่มีพิกัดส่งมา จะนำค่า default มาแสดงแทน

คุณสามารถดูตัวอย่างได้จาก google maps: HTML5.Geolocation หรือลองสร้างไฟล์ตามตัวอย่าง

<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>Google Maps APIs > Maps JavaScript API > HTML5 Geolocation</title>
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
<link href="styles.css" rel="stylesheet" type="text/css">
</head>
<body>
<div class="container">
  <div class="row">
    <div class="col-md-2">
      <h3>HTML5 Geolocation</h3>
      <dl>
        <dt>latitude</dt>
        <dd id="latitudeA"></dd>
        <dt>longitude</dt>
        <dd id="longitudeA"></dd>
      </dl>
    </div>
    <div class="col-md-10 map" id="mapA"></div>
  </div>
</div>
<script>
var infoWindow;
var map;
var position = false;

/* function createMaps */
function createMap(mapArea, position) {
    var latlng = new google.maps.LatLng(parseFloat(position.latitude), parseFloat(position.longitude));
    var infoWindow = new google.maps.InfoWindow;
    var map = new google.maps.Map(document.getElementById(mapArea), {
        "zoom": 18
    });

    map.setCenter(latlng);

    var marker = new google.maps.Marker({
        "map": map,
        "position": latlng,
    });

    infoWindow.open(map, marker);
    infoWindow.setContent('Latitude = ' + position.latitude + ', ' + ' longitude = ' + position.longitude);
    infoWindow.setPosition(latlng);
}

/* get position from HTML5 Geolocation */
function getGeolocation() {
    var position = false;
    if (navigator.geolocation) {
        navigator.geolocation.getCurrentPosition(function(successCallback, errorCallback) {

            position = successCallback.coords;

            document.getElementById('latitudeA').innerHTML = successCallback.coords.latitude;
            document.getElementById('longitudeA').innerHTML = successCallback.coords.longitude;

        }, function() {
            handleLocationError(true, infoWindow, map.getCenter());
        });
    } else {
        /*Browser doesn't support Geolocation*/
        handleLocationError(false, infoWindow, map.getCenter());

        return false;
    }

    return position;
}

/* show error of HTML5 Geolocation */
function handleLocationError(browserHasGeolocation, infoWindow, position) {
    infoWindow.setPosition(position);
    infoWindow.setContent(browserHasGeolocation ?
        'Error: The Geolocation service failed.' :
        'Error: Your browser doesn\'t support geolocation.');
    infoWindow.open(map);
}

function initMap(defaultLatitude = 13.7651991, defaultLongitude = 100.5368694) {
    position = getGeolocation();

    if (position == false || position == null || position === undefined || position.length <= 0 || typeof position == 'undefined') {
        position = {
            "latitude": defaultLatitude,
            "longitude": defaultLongitude,
        };
    }

    createMap('mapA', position);
}
</script>
<script async defer src="https://maps.googleapis.com/maps/api/js?callback=initMap&key=AIzaSyDE5zb4r9sbN5S_GcD3NZRED1Ma3FzFqso"></script>
</body>
</html>

google maps: basic

พื้นฐานการสร้าง google maps จาก Maps JavaScript API

  1. เริ่มจาก ขอ Google API key ก่อน
  2. จากนั้นลองสร้าง code ง่ายๆ ตามตัวอย่าง
    .map {
    	border: #c00000 solid thin;
    	height: 100%;
    	min-height: 500px;
    }

    ส่วนนี้จะเป็น css class ที่ทำให้แน่ใจว่า พื้นที่แสดงแผนที่เห็นสูงอย่างน้อย 500 px เพราะว่าตัว google maps จะเอาความสูงของ div เอาไปใช้ ถ้าไม่กำหนดไว้ถึงสร้างแผนที่ได้ก็อาจจะมองไม่เห็น

  3. สร้างพื้นที่ไว้แสดงแผนที่ กำหนดให้ id=”mapA”
    <div class="map" id="mapA"></div>
  4. สร้างแผนที่
    /* function createMaps */
    function createMap(mapArea, position) {
        var latlng = new google.maps.LatLng(parseFloat(position.latitude), parseFloat(position.longitude));
        var infoWindow = new google.maps.InfoWindow;
        var map = new google.maps.Map(document.getElementById(mapArea), {
            "zoom": 8
        });
    
        map.setCenter(latlng);
    
        var marker = new google.maps.Marker({
            "map": map,
            "position": latlng,
        });
    
        infoWindow.open(map, marker);
        infoWindow.setContent('Latitude = ' + position.latitude + ', ' + ' longitude = ' + position.longitude);
        infoWindow.setPosition(latlng);
    }
    
    function initMap(defaultLatitude = 13.7651991, defaultLongitude = 100.5368694) {
        position = {
            "latitude": defaultLatitude,
            "longitude": defaultLongitude,
        };
    
        createMap('mapA', position);
    }
  5. โหลด Google Maps APIs โดยใช้
    <script async defer src="https://maps.googleapis.com/maps/api/js?callback=initMap&key=YOUR_API_KEY"></script>

    แทนที่ YOUR_API_KEY เป็น key ของเราเอง โดย callback= ตามด้วย function ที่ใช้สร้างแผนที่ หรือ function อื่นๆ ที่จะให้ทำงานหลังโหลดเพจเสร็จ ตามตัวอย่างคือ function initMap จะไม่สร้างแผนที่โดยตรงแต่จะใช้ function createMap สร้างให้อีกที ถ้าต้องการสร้างแผนที่เพิ่ม แค่เรียกใช้

    createMap('พื้นที่แสดงแผนที่', {
        "latitude": ค่า latitude,
        "longitude": ค่า longitude,
    });

    ก็จะมีแผนที่ที่มีหน้าตาคล้ายๆอีกอันเพิ่มขึ้นมา

ดูตัวอย่างได้ที่ google maps: basic หรือจะลองสร้างไฟล์เอง

@charset "utf-8";
/* CSS Document */
.map {
	border: #c00000 solid thin;
	height: 100%;
	min-height: 500px;
}
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>Google Maps APIs > Maps JavaScript API > Simple Map</title>
<link href="styles.css" rel="stylesheet" type="text/css">
</head>
<body>
<div class="map" id="mapA"></div>
<script>
/* function createMaps */
function createMap(mapArea, position) {
    var latlng = new google.maps.LatLng(parseFloat(position.latitude), parseFloat(position.longitude));
    var infoWindow = new google.maps.InfoWindow;
    var map = new google.maps.Map(document.getElementById(mapArea), {
        "zoom": 8
    });

    map.setCenter(latlng);

    var marker = new google.maps.Marker({
        "map": map,
        "position": latlng,
    });

    infoWindow.open(map, marker);
    infoWindow.setContent('Latitude = ' + position.latitude + ', ' + ' longitude = ' + position.longitude);
    infoWindow.setPosition(latlng);
}

function initMap(defaultLatitude = 13.7651991, defaultLongitude = 100.5368694) {
    position = {
        "latitude": defaultLatitude,
        "longitude": defaultLongitude,
    };

    createMap('mapA', position);
}
</script>
<script async defer src="https://maps.googleapis.com/maps/api/js?callback=initMap&key=AIzaSyDE5zb4r9sbN5S_GcD3NZRED1Ma3FzFqso"></script>
</body>
</html>

การกำหนด function ให้ทำงานหลังโหลดเพจเสร็จนอกจากใช้ตัวแปร callback แล้วยังสามารถใช้

google.maps.event.addDomListener(window, 'load', initMap);

ได้เหมือนกัน

อ่านเพิ่มเติม

เก็บ CodeIgniter log ด้วย hook

วิธีที่จะพัฒนาและดูแลเว็บคือทำระบบ log ที่จะบันทึกข้อมูลการใช้งาน ซึ่งจะบันทึกสิ่งที่เกิดขึ้นไว้สำหรับเข้ามาดูเหตุการณ์ที่เกิดขึ้นย้อนหลังได้ มีประโยชน์ในการดูแลระบบโดยเฉพาะตัวที่เป็น API ให้ระบบอื่นๆเรียกใช้ สำหรับ ci เราสามารถเขียนได้โดยการใช้ hook

ระบบ Hook (ตะขอ) คือ เป็นตะขอที่เกี่ยวกับเหตุการณ์ซักอย่างแล้วจึงทำงาน เหมือนกับ tricker ในดาต้าเบส หรือจะ อธิบายการทำงานง่ายๆ ก็เหมือน คุณไปที่ร้านอาหารคิดไม่ออกว่าจะทานอะไร ใช้วิธีรอให้เพื่อนสั่งแล้วบอกว่า หมีทู่ (me too.) นั้นละครับ

  1. ก่อนอื่น ไปเปิดการใช้งาน hook ก่อน เปิดไฟล์ /application /config/config.php แก้ $config[‘enable_hooks’] = FALSE; เป็น TRUE;
  2. เปิดไฟล์ /application /config/ hooks.php เพิ่มบรรทัด
    $hook['post_system'][] = array(
            'class' => '',
            'function' => 'log_Profiling',
            'filename' => 'log_Profiling.php',
            'filepath' => 'hooks'
    );
    
  3. จากนั้นไปสร้างไฟล์ log_Profiling.php ใน /application/hooks เนื้อหาตามนี้ครับ
    <?php
    function log_Profiling(){
    global $CI ,$application_folder;
    	$output = '<html><body>';
    	$output .= '<fieldset id="ci_profiler_benchmarks" style="border:1px solid #c00000;padding:6px 10px 10px 10px;margin:20px 0 20px 0;background-color:#eee">
    <legend style="color:#c00000;">OUTPUT</legend>'.$CI->output->get_output().'</fieldset>';
    	if( ! isset($_POST['profiler'])){
    		$CI->load->library('profiler');
    		if ( ! empty($CI->_profiler_sections)){
    			$CI->profiler->set_sections($this->_profiler_sections);
    		}
    		$output .= $CI->profiler->run();
    	}
    	$output .= '</body></html>';
    	$fp = fopen($application_folder.'/logs/Profiling/'.date('Y-m-d-H-i-s-U').'_'.$CI->router->fetch_class().'_'.$CI->router->fetch_method().'.html' ,'w');
    	flock($fp ,LOCK_EX);
    	fwrite($fp ,$output);
    	flock($fp ,LOCK_UN);
    	fclose($fp);
    }
    
  4. สร้างโฟลเดอร์ application/logs/Profiling/

แก้ระบบ error ของ CodeIgniter สำหรับ API

เดิมการแสดง error จะแสดงออกมาเป็น html แต่เมื่อเรานำ CI มาใช้เป็นระบบ API ถ้าแสดงแบบเดิมจะเกิดปัญหา app บน iphone / ipad ล่ม เพราะว่าข้อมูลที่ส่งออกไป กับที่ ios ทั้งหลายคาดว่าจะได้รับไม่ตรงกัน แก้ไม่ยากครับ แค่แทนที่ไฟล์ใน application/errors จาก code ด้านล่าง

error_404.php

<?php
global $application_folder;

// Get an article from the database, show a 404 page if the requested article was not found.

	$article = get_content($this->uri->uri_string());

	if(empty($article)){
		show_404($this->uri->uri_string());
	}

$datas['status'] = -1;
$datas['type'] = '404';
$datas['messageHeading'] = '404';
$datas['message'] = 'file not found';

$logs = "nnn".date('Y-m-d H:i:s');
$logs = $logs."ntURLt".$_SERVER['SERVER_ADDR'].$_SERVER['REQUEST_URI'];
$logs = $logs."ntPOSTt".print_r($_POST ,true);
$logs = $logs."ntRETURNt".print_r($datas ,true);
$fp = fopen($application_folder.'/logs/error'.date('Y-m-d').'.txt' ,'a+');
flock($fp ,LOCK_EX);
fwrite($fp ,$logs);
flock($fp ,LOCK_UN);
fclose($fp);
exit(json_encode($datas));

error_db.php

<?php
global $application_folder;
$datas['status'] = -1;
$datas['code'] = 1;
$datas['type'] = 'database';
$datas['messageHeading'] = $heading;
$datas['message'] = 'database error';
$datas['message'] = $message;

$logs = "nnn".date('Y-m-d H:i:s');
$logs = $logs."ntURLt".$_SERVER['SERVER_ADDR'].$_SERVER['REQUEST_URI'];
$logs = $logs."ntPOSTt".print_r($_POST ,true);
$logs = $logs."ntRETURNt".print_r($datas ,true);
$fp = fopen($application_folder.'/logs/error'.date('Y-m-d').'.txt' ,'a+');
flock($fp ,LOCK_EX);
fwrite($fp ,$logs);
flock($fp ,LOCK_UN);
fclose($fp);
exit(json_encode($datas));

error_general.php

<?php
global $application_folder;
$datas['status'] = -1;
$datas['type'] = 'general';
$datas['messageHeading'] = $heading;
$datas['message'] = $message;

$logs = "nnn".date('Y-m-d H:i:s');
$logs = $logs."ntURLt".$_SERVER['SERVER_ADDR'].$_SERVER['REQUEST_URI'];
$logs = $logs."ntPOSTt".print_r($_POST ,true);
$logs = $logs."ntRETURNt".print_r($datas ,true);
$fp = fopen($application_folder.'/logs/error'.date('Y-m-d').'.txt' ,'a+');
flock($fp ,LOCK_EX);
fwrite($fp ,$logs);
flock($fp ,LOCK_UN);
fclose($fp);
exit(json_encode($datas));

error_php.php

<?php
global $application_folder;
$datas['status'] = -1;
$datas['type'] = 'php';
$datas['messageHeading'] = 'A PHP Error was encountered';
$datas['message'] = $message;
$datas['severity'] = $severity;
$datas['filepath'] = $filepath;
$datas['line'] = $line;

$logs = "nnn".date('Y-m-d H:i:s');
$logs = $logs."ntURLt".$_SERVER['SERVER_ADDR'].$_SERVER['REQUEST_URI'];
$logs = $logs."ntPOSTt".print_r($_POST ,true);
$logs = $logs."ntRETURNt".print_r($datas ,true);
$fp = fopen($application_folder.'/logs/error'.date('Y-m-d').'.txt' ,'a+');
flock($fp ,LOCK_EX);
fwrite($fp ,$logs);
flock($fp ,LOCK_UN);
fclose($fp);
exit(json_encode($datas));

เท่านี้ error ทั้งหลายจะแปลงกายไปเป็น แบบ json และบันทึกลงไฟล์ใน application/logs เป็นรายวัน

api,framework,ide,library

คำพวกนี้จะเจอเป็นประจำในคู่มือเวลาเขียนเว็บดูเหมือนจะมีความหมายเดียวกัน ความจริงมีรายละเอียดมีแตกต่างกันบ้างโดย

  • ฟังชั่น (function) คือชุดคำสั่งที่ทำหน้าที่ใดหน้าที่หนึ่งโดยเฉพาะ
  • เอพีไอ (api) คือส่วนติดต่อที่อนุญาติให้เอาไปใช้เวลาเขียนโปรแกรม เช่นเอพีไอที่ทำหน้าที่ติดต่อกับดาด้าเบส ตัวทีทำหน้าที่ติดต่อกับกล้องถ่ายภาพ แต่ละเอพีไอประกอบด้วยฟังก์ชั่นทำหน้าที่ต่างๆ กันไป
  • ไลบารี (library) เปรียบเหมือนห้องสมุดที่มีชุดคำสั่งสำรับอำนวยความสดวกในการเขียนด้านใดด้านหนึ่ง เหมือนห้องสมุดจะมีหนังหนังสือให้เลือกใช้ เช่น jquery เป็นไลบารี (ห้องสมุดเฉพาะด้าน javascript) ซึ่งมีเอพีไอ (หนังสือ) ให้เรียกใช้ เช่น Selectors ไว้เลือกส่วนต่างๆ ของโค้ท html มี Manipulation ไว้แก้ไขโค้ท html
  • เฟรมเวิร์ค (framework) ตามตัวอักษรคือ กรอบการทำงาน โครงสร้างหรือจะเรียกว่านั่งร้านก็ได้ โดยจะต่างจากไลบารีเพราะ
    1. คำสั่งเราจะอยู่ในเฟรมเวิร์ค(กรอบ) ได้เพียงตัวเดียวเท่านั้นต่างจากไลบารีที่สามารถเรียกใช้พร้อมกันหลายตัว
    2. การทำงานจะไม่มีการเรียกใช้เอพีไอโดยตรงแต่จะผ่านเฟรมเวิร์คทำให้สามารถเขียนครั้งเดียวเอาไปใช้ในสภาพแวดล้อมต่างกันได้ เช่น ภาษาตะกูลดอทเน็ตคำสั่งของเราจะไม่เรียกใช้เอพีไอของวินโดวส์โดยตรงแต่จะผ่านดอทเน็ตเฟรมเวิร์คอีกที ทำให้ถึงเป็นวินโดวส์ต่างเวอร์ชั่นกัน แต่ถ้าเฟรมเวิร์คเวอร์ชั่นเดียวกันเป็นล่ามให้ก็จะทำงานเหมือนกัน
  • ไอดีอี (ide) คือสภาพแวดล้อมที่เราใช้เขียนโปรแกรม ประกอบด้วยหลายส่วน
    • เครื่องมือที่เราใช้เขียนโปรแกรม เช่น อิดิพลัส ,ดรีมวีฟเวอร์ ,วิชวลสตูดิโอ
    • ตัวแปรภาษาต่างๆ
    • ตัวดีบัคโค้ท
    • เซิร์ฟเวอร์