From Northern Country

北海道生まれのエンジニアのブログです

Kotlinを勉強する際に役立つリソース集

この記事の目的

この記事では私がKotlinを学ぶ上で特に良いと感じたリソースについて紹介します。 紹介できていないリソースで素晴らしいものはたくさんあると思うのでおすすめのものがあればコメント頂ければ嬉しいです。

Kotlinのリソースまとめ

公式ドキュメント

まずは公式のドキュメントです。 英語版ももちろん充実していますが、日本語版も有志が翻訳をしてくれています。 感謝の気持ちでいっぱいです。

Kotlin 公式ドキュメント
Kotlin 公式ドキュメント日本語訳

問題集

Kotlin Koans Online

Kotlinで問題を解きながら学習できるサイトです。 ブラウザ上で実際にコードを実行しながら学べます。

Kotlin Koans Online

記事

Kotlin入門までの助走読本

日本Kotlinユーザグループが提供しているPDFです。 書籍はまるでオライリーと勘違いするクオリティです。 Kotlinに入門する上で非常に素晴らしい書籍で、サンプルコードも充実しているので 基本的な文法はマスターすることが出来ると思います。

Kotlin入門までの助走読本

Kotlinベストプラクティス

この記事はKotlinらしくKotlinを書くベストプラクティスが書かれた英語記事の日本語訳です。 Kotlinらしいコードの書き方を学べる記事です。

翻訳: Kotlinベストプラクティス『Idiomatic Kotlin. Best Practices』

最近話題の「Kotlin」は本当に業務に使えるの? ―国内第一人者と「Yahoo!ニュース」Android版開発者が語るKotlin開発実践のコツ

実際に業務で使う際のポイントについて語られています。

最近話題の「Kotlin」は本当に業務に使えるの? ―国内第一人者と「Yahoo!ニュース」Android版開発者が語るKotlin開発実践のコツ

Qiita

JavaプログラマのためのKotlin入門

三部構成になっており、JavaとKotlinを比較している記事です。 JavaエンジニアがKotlinを始める際におすすめです。

JavaプログラマのためのKotlin入門
JavaプログラマがKotlinでつまづきがちなところ
JavaプログラマがKotlinで便利だと感じること

Android開発を受注したからKotlinをガッツリ使ってみたら最高だった

Android開発にKotlinを使用した際について書かれています。 Kotlinの良い点についてまとめられており、Kotlinを使用すればどのように便利になるのか簡単に理解できます。

Android開発を受注したからKotlinをガッツリ使ってみたら最高だった

書籍

Kotlinスタートブック

日本Kotlinユーザグループ代表を務める長澤太郎さんが執筆されたAndroidプログラミングにおけるKotlinについての書籍です。 Kotlinの文法と機能を幅広く解説し、その後実際にQiitaクライントアプリを開発していきます。

Andorid開発者やAndroidに興味があるならこの本からKotlinデビューするのがおすすめです。 初心者でもスムーズにAndroidアプリ開発の世界に入門することが出来ます。

ただ2016年出版で少し古くなっているので改定を待ちたいところです。

Kotlinスタートブック

Kotlin Webアプリケーション 新しいサーバサイドプログラミング

先ほど紹介したKotlinスタートブックと同じく、日本Kotlinユーザグループ代表を務める長澤太郎さんが執筆されています。 この本はサーバサイドKotlinについての書籍で、Webアプリケーションの開発を体験する内容になっています。

サーバーサイドKotlinについての本は少ないので、サーバーサイドKotlinに興味のある方は必見です。

Kotlin Webアプリケーション 新しいサーバサイドプログラミング

Androidアプリ開発のためのKotlin実践プログラミング 現場で求められる設計・実装のノウハウ

こちらはKotlinでAndroidアプリ開発についての書籍です。 先ほど紹介したKotlinスタートブックと比べて、こちらのほうがより実践的な内容になっているのでKotlinでAndroidアプリ開発を本格的に学びたい方にはおすすめです。 Kotlinらしいコードを書くノウハウと、既存アプリをKotlinに移行させるノウハウを解説しています。

Androidアプリ開発のためのKotlin実践プログラミング 現場で求められる設計・実装のノウハウ

Kotlinイン・アクション

今まで紹介したホントは少し毛色が変わり、より言語仕様について内容になっています。 著者は、Kotlinの開発当初から関わっており、Kotlinを知り尽くした人物です。 そのため、機能や文法だけではなく、内部的な仕様や思想についても書かれており、より深くKotlinという言語を学びたい方はぜひ手にとって見てください。

Kotlinイン・アクション

サイト

逆引きKotlin

最近は更新がされていないようですが、このような逆引きサイトもあります。 逆引きKotlin

終わりに

紹介してきたようにKotlinを学ぶ上で素晴らしいリソースはたくさんあります。 今後も随時更新して行きたいと思います。

SpringBoot × KotlinでHTTP通信を行う

概要

Spring BootのWebアプリから、外部のAPIにHTTP通信する方法についてです。

RestTemplateを使用するClassを定義

import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.web.client.RestTemplateBuilder
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.stereotype.Service
import org.springframework.web.client.RestTemplate

@Configuration
class ExampleConfiguration {
    @Bean
    fun restTemplate(): RestTemplate = RestTemplate()

    @Bean
    fun restTemplate(builder: RestTemplateBuilder): RestTemplate {
        return builder.build()
    }

}

@Service
open class SearchService @Autowired constructor(private val restTemplate: RestTemplate){

    fun search(q: String): ResponseEntity<String> {
        return restTemplate.getForObject("{url}", String::class.java)
    }

}

RestTemplateで使用できるメソッド

RestTemplateで使用できるメソッドが2つあります。 1つ目は、外部サービスのJSONを、そのまま(文字列のまま)レスポンスとして返しています。

restTemplate.exchange(url, HttpMethod.GET, null, String.class)

JSONValueクラスにマッピングしたうえで、レスポンスとして返しています。

restTemplate.getForObject("{url}", Value::class.java)

Spring Boot + KotlinでSpring Securityを使い認証処理を行う

はじめに

Spring BootとKotlinでSpring Securityを使い認証を行う方法についてメモです。 必要なクラスを列挙しているだけで説明少ないです。

実装

View

まずはViewを作成します。

<!DOCTYPE html>
<html lang="ja" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8"/>
    <title>ログインページ</title>
</head>
<body>
<h1>トップページ</h1>
<form id="loginForm" method="post" th:action="@{/users/login}">
    <input type="text" name="email" />
    <input type="password" name="password"/>
    <input type="submit" value="ログイン"/>
</form>
</body>
</html>

ログイン成功時に遷移するViewです。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8"/>
    <title>ログインサクセスページ</title>
</head>
<body>
<h1>ログイン成功</h1>
<a href="/logout">ログアウト</a>
</body>
</html>

build.gradel

必要なライブラリを読み込みます。 Spring Security というライブラリで認証処理が行えます。 データベース接続はSpring Data JPAを使い、テンプレートエンジン(Thymeleaf)を利用します。

compile('org.springframework.boot:spring-boot-starter')
compile('org.springframework.boot:spring-boot-starter-web')
compile('org.springframework.boot:spring-boot-starter-data-jpa')
compile('org.springframework.boot:spring-boot-starter-security')
compile "org.springframework.boot:spring-boot-starter-thymeleaf:${springBootVersion}"
compile("org.jetbrains.kotlin:kotlin-stdlib-jdk8")
compile("org.jetbrains.kotlin:kotlin-reflect")
compile('com.fasterxml.jackson.module:jackson-module-kotlin')
compile('mysql:mysql-connector-java:5.1.6')
testCompile('org.springframework.boot:spring-boot-starter-test')
testCompile('org.springframework.security:spring-security-test')

データベース接続の設定

データベース接続の設定です。 今回はMySqlで、ローカルのsampleデータベースを利用します。 必要に応じて設定値を入れ替えてください。

spring:
  datasource:
    url:  jdbc:mysql://localhost/sample
    username: root
    driverClassName: com.mysql.jdbc.Driver

DB

DBにUsersテーブルを作成しておきます サンプルなので平文でパスワードを保存してますが、実際はhash値を保存する必要があります。

create table if not exists users (
  id int primary key,
  email varchar(255),
  password varchar(255),
  created_at datetime,
  updated_at datetime
);

insert into users
VALUES( 1 , 'test@example.com', 'password', 26 , 1 , NOW() , NOW()),

Entity

Usersテーブルに対応したEntityクラスを用意します。

package com.sample.Entity

import javax.persistence.*

@Entity
@Table(name = "users")
data class User (
        @Id @GeneratedValue var id: Int? = 0,
        @Column(nullable = false) var email: String = "",
        @Column(nullable = false) var password: String = "",
)

Repository

Usersテーブルからデータを取得するためインターフェイスを作成します。 JpaRepositoryに準拠することで、基本的なCRUD処理は自動で実装されます。

package com.sample.Repository

import com.sample.Entity.User
import org.springframework.data.jpa.repository.JpaRepository
import org.springframework.stereotype.Repository

@Repository
interface UserRepository: JpaRepository<User, Long> {
    fun findByEmail(email: String): User
}

Service

UserRepositoryを呼び出して、データを取得するServiceクラスを作成します。

package com.sample.Service

import com.sample.Entity.User
import com.sample.Repository.UserRepository
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.stereotype.Service

@Service
open class UserService @Autowired constructor(private val userRepository: UserRepository) {

    /**
     * emailでUserテーブルを検索
     * @return User
     */
    fun findByEmail(email: String): User = userRepository.findByEmail(email)

}

UserDetailsServiceの実装クラス Spring Securityでのユーザー認証に使用します。

package com.sample.Service

import com.sample.Entity.User
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.security.core.userdetails.UserDetails
import org.springframework.security.core.userdetails.UserDetailsService
import org.springframework.security.core.userdetails.UsernameNotFoundException
import org.springframework.stereotype.Component

@Component
open class UserDetailsServiceImpl :UserDetailsService {

    @Autowired
    lateinit var userService: UserService

    override fun loadUserByUsername(email : String ) : UserDetails {
        // 認証を行うユーザー情報を格納する
        var user : User?  = null
        try {
            // 入力したemailからユーザーを取得
            user = userService.findByEmail(email)
        } catch (e:Exception ) {
            throw UsernameNotFoundException("can't get User")
        }

        // ユーザー情報を取得できなかった場合
        if(user == null) {
            throw UsernameNotFoundException("User does not exists")
        }

        // ユーザー情報が取得できたらSpring Securityで認証できる形で戻す
        return LoginUser(user)
    }

}

認証ユーザーの情報を格納するクラス

package com.sample.Service

import com.sample.Entity.User
import org.springframework.security.core.authority.AuthorityUtils;

class LoginUser (user: User): org.springframework.security.core.userdetails.User( user.email, user.encrypted_password,
        AuthorityUtils.createAuthorityList("ROLE_USER")) {

    var loginUser: User? = null

    init{
        this.loginUser = user
    }

}

Controller

package com.sample.Controller

import com.sample.Service.UserService
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.stereotype.Controller
import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.servlet.ModelAndView

@Controller
class LoginController @Autowired constructor(private val userService: UserService) {

    @RequestMapping("/")
    fun root(): ModelAndView {
        return ModelAndView("/index")
    }

    @RequestMapping("/login/success")
    fun success(): ModelAndView = ModelAndView("/login/success")

}

Config

Spring Security設定クラスです。 認証処理をかけたいパスなどもここで設定をします。

package com.sample

import com.sample.Service.UserDetailsServiceImpl
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder
import org.springframework.security.config.annotation.authentication.configuration.GlobalAuthenticationConfigurerAdapter
import org.springframework.security.config.annotation.web.builders.HttpSecurity
import org.springframework.security.config.annotation.web.builders.WebSecurity
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter
import org.springframework.security.crypto.password.NoOpPasswordEncoder
import org.springframework.security.crypto.password.PasswordEncoder
import org.springframework.security.web.util.matcher.AntPathRequestMatcher

@Configuration
@EnableWebSecurity
open class SecurityConfig: WebSecurityConfigurerAdapter() {

    override fun configure(web: WebSecurity) {
        // ここに設定したものはセキュリティ設定を無視
        web.ignoring().antMatchers(
            "/**/favicon.ico",
            "/images/**",
            "/css/**",
            "/javascript/**",
            "/webjars/**"
        )
    }

    override fun configure(http: HttpSecurity) {
        // 許可の設定
        http.authorizeRequests()
                .antMatchers("/", "/index").permitAll() // indexは全ユーザーアクセス許可
                .anyRequest().authenticated()  // それ以外は全て認証無しの場合アクセス不許可

        // ログイン設定
        http.formLogin()
                .loginProcessingUrl("/users/login") // 認証処理のパス
                .loginPage("/index")   // ログインフォームのパス
                .failureHandler(AuthenticationFailureHandler()) // 認証失敗時に呼ばれるハンドラクラス
                .defaultSuccessUrl("/login/success") // 認証成功時の遷移先
                .usernameParameter("email").passwordParameter("encrypted_password") // ユーザー名、パスワードのパラメータ名
                .and()

        // ログアウト
        http.logout()
                .logoutRequestMatcher(AntPathRequestMatcher("/logout**"))
                .logoutSuccessUrl("/index")
    }

    @Configuration
    open class AuthenticationConfiguration : GlobalAuthenticationConfigurerAdapter() {
        @Autowired var userDetailsService : UserDetailsServiceImpl = UserDetailsServiceImpl() ;

        @Bean
        fun passwordEncoder(): PasswordEncoder {
            return NoOpPasswordEncoder.getInstance()
        }

        override fun init(auth: AuthenticationManagerBuilder) {
            auth.userDetailsService(userDetailsService)
        }
    }

}

Handler

Spring Securityの認証失敗時に呼ばれるハンドラクラスです。

package com.sample

import javax.servlet.ServletException
import javax.servlet.http.HttpServletRequest
import javax.servlet.http.HttpServletResponse
import org.springframework.security.authentication.BadCredentialsException
import org.springframework.security.core.AuthenticationException
import org.springframework.security.web.authentication.AuthenticationFailureHandler
import java.io.IOException


class AuthenticationFailureHandler : AuthenticationFailureHandler {

    @Throws(IOException::class, ServletException::class)
    override fun onAuthenticationFailure(httpServletRequest: HttpServletRequest,
                                         httpServletResponse: HttpServletResponse,
                                         authenticationException: AuthenticationException) {
        var errorCode = ""

        // ExceptionからエラーIDをセットする
        if (authenticationException is BadCredentialsException) {
            errorCode = "403"
        }
        httpServletResponse.sendRedirect(httpServletRequest.contextPath + "/index?error=" + errorId)
    }
}

まとめ

Spring Securityを使えば認証処理をシンプルな形ですが実装できました。

技術書典5にサーバサイドKotlin本で出展します!

報告

記事のタイトルの通り、技術書典5に出展します。 出展は初めての経験ですが、東京に来たら技術書展に参加したいとずっと思っていたので、思い切って参加を決めました。

どんな本を書く?

サーバサイドKotlin本を書きます。 対象としては、サーバサイド、もしくはKotlin、Spring Bootの初心者を想定しています。

具体的に言うとSpring Boot × KotlinでWebアプリケーションやAPIを実際に作成しながら学んでいく書籍にしたいなーと思っています。
サーバーサイド、Kotlin、Spring Boot初心者の方でも手を動かしながら実際に開発を進めていけるような構成にするつもりです。

1章: Kotlinの文法
2章: Spring Bootの基本
3章: Webアプリケーションの開発
4章: APIの開発

ざっくりと以上のような構成になると思います。
Webアプリケーション開発の題材には王道のTodoアプリを採用し、Spring Securityを用いて認証機能の実装まで行います。
4章のAPIはまだどのようなものを題材にするか決めていないですが、CORS対応やトークン認証などはシンプルな形で実装したいと考えています。

最後に

色々と不安も多いですが、折角の機会なので楽しみたいと思います。 よろしければぜひ、け58ブースに来て、サーバサイドKotlinに入門してみてください!

Laravelでユーザー登録・認証機能を作成する(たぶん)最も簡単な方法

はじめに

Laravelでユーザー登録・認証機能作成する方法をメモとして残しておきます。 タイトルの通り、(たぶん)このやり方が最も簡単でシンプルな方法なのではないかと思います。

LaravelにはデフォルトでAuthというものが存在しており、artisanも活用すればあっという間にユーザー登録機能が完成してしまいます。

Viewを作成する

以下のコマンドを叩くだけで認証関係のViewがまとめて作成されます。 ログイン後に飛ばされるページはAuthディレクトリ配下のそれぞれのControllerで勝手に定義してあるので、そこは適宜修正を行います。

$ php artisan make:auth --views

ルーティングを行う

ルーティングを行っている場所で以下のRouteファサードを追加するだけです。

Route::auth(); 

このような形でOKです。

Route::group(['middleware' => ['web']], function() {

    Route::auth();

    Route::get('/', function() {
        $books = Book::all();
        return view('books', ['books' => $books]);
    });

});

ログイン状態の確認を行う

Route::get('/', ['middleware' => 'auth', function() {
    $books = Book::all();
    return view('books', ['books' => $books]);
}]);

ルーティングでこのように記述することで、function()で処理を行う前にログイン状態の確認を行ってくれます。 ログイン状態の時のみfunction()内の処理が実行され、ログインしていなければAuthディレクトリ内のcontrollerに定義されているパスにリダイレクトされます。

protected $redirectTo = '/';

再起動して確認する

再起動して確認してみます。 

$ php artisan serve

Login処理が行えれば成功です。

Laravelで開発を行う際に覚えておきたいことまとめ

はじめに

最近Laravelを触り始めたので、自分用の開発リファレンスとしてまとめてみました。 Railsも良いんですが、やっぱりPHPの気軽さには惹かれました。 しばらくサーバーサイドはLaravelでやっていきたいです。

laravelのインストール

https://qiita.com/morisuke/items/bedb27418a65f6924fb1

プロジェクトの作成

$ composer create-project laravel/laravel projectname

以下の記事を参考にlaravelコマンドをインストールすると、もう少し簡単に作成することもできます Laravel5のインストール方法とディレクトリ構成について

$ laravel new projectname

ローカルサーバーを起動する

以下のコマンドでlaravelに内蔵のWebサーバーが立ち上がります。

$ php artisan serve

コントローラーの作成

これで指定した名前でControllerが作成されます。

$ php artisan make:controller MyController

--resource(-r)」オプションを追加すると、リソースコントローラーが作成されます。 リソースコントローラーでは、show()、update()等のメソッドの引数にあらかじめモデルクラスが定義されています。

$ php artisan make:controller MyController --resource

APIリソースの作成

https://qiita.com/zdjjs/items/1c2437fcdd35c6754bcf

ルーティング

デフォルトではroutesディレクトリの中にある、web.phpの中でルーティング処理が記述されています。

Route::get('/', function () {
    return view('books');
});

ルーティングする際にDBからデータを取ってきてViewに渡したり、saveやdeleteも可能です。 簡単なアプリならControllerなしで書けるので便利そうです。

use App\Book;
use Illuminate\Http\Request;

Route::group(['middleware' => ['web']], function() {

    Route::get('/', function() {
        $books = Book::all();
        return view('books', ['books' => $books]); // Viewでbooksという変数名でアクセス可能
    });


    Route::post('book', function(Request $request) {
        $validator = Validator::make($request->all(), [
                'name' => 'required | max: 255',
        ]);

        if ($validator->fails()) {
            return redirect('/')
                ->withInput()
                ->withErrors($validator);
        }

        $book = new Book();
        $book->title = $request->name;
        $book->save();
        return redirect('/');
    });

    Route::delete('/book/{book}', function(Book $book) {
        $book->delete();
        return redirect('/');
    });

});

パラメーターの受け取り

パラメーターの受け取りは2つの方法で行うことができます。 違いはURLが異なることです。

アドレスにルートパラメーターを定義

show/01という形でアクセスできます。

public function show($id) {
  $data = ['id' => $id ];
  return view('show', $data);
}
Route::get('show/{id?}', 'UserController@show');

クエリー文字列の利用

show?id=01という形でアクセスできます。

public function show(Request $request) {
  $data = ['id' => $request->id ];
  return view('show', $data);
}
Route::get('show', 'UserController@show');

データベース

テーブルを作成するクラスを生成

以下のコマンドでbooksテーブルを作成するcreateBooksTableクラスをDatabase/migrationsディレクトリに生成してくれます。

$ php artisan make:migration create_books_table --create=books

マイグレーション実行時に、以下のメソッド内で実際のテーブル作成が行われるのでここに追加したいカラムを追加することが出来ます。

public function up() {
  Schema::create('books', function (Blueprint $table) {
    $table->increments('id');
    $table->string('title'); // titleカラムを追加
    $table->timestamps();
  });
}

マイグレーションの実行

$ php artisan migrate

モデルの作成

booksテーブルに対応するモデルを作成します。 appディレクトリ配下にBook.phpが追加されます。 先頭文字は大文字にし、単数形にする必要があるので注意して下さい。

$ php artisan make:model Book

--controller(-c)オプションで、モデルクラスに対応したコントローラーが作成されます。 さらに、コントローラーの作成と同じように、--resource(-r)」オプションでリソースコントローラーの作成も可能です。

$ php artisan make:model Book --controller --resource

Model\Bookとすると自動的にapp/Model/Book.phpとして作成してくれます。 オプションとして--factory(-f)を指定することで,Factoryも一緒に作ることができます。

$ php artisan make:model "Model\Post" --factory

Factoryの作成

モデルの生成時にではなく、Factory単体で作成することも出来ます。 -mオプションモデルを指定することで生成時に自動的に使用するようにしてくれます。

$ php artisan make:factory BookFactory

シーダーの定義

手動でも作成できますが、ミスを防ぐためにもコマンドで自動生成しましょう。 database/seedsディレクトリに設置されます。 runメソッドの中でデータベースにデータを挿入することが出来ます。

$ php artisan make:seeder UsersTableSeeder

データベース接続設定

SQLite3対応

プロジェクトのルートフォルダ内の、.envファイルを開いて以下の指定をします。 この時DB_DATABASEでは、データベースファイルを一番上の階層から絶対パスで指定する必要があります。 たとえば、/home/username/projectname にLaravelのプロジェクトを作成した場合は以下のようなパスを指定する必要があります。

DB_CONNECTION = sqlite
DB_DATABASE=/home/username/projectname/database/database.sqlite

このやり方でsqliteを使う際は、デフォルトで記述されている以下の設定はいらないようなので削除しておきます。(Laravel同梱のsqliteで、usernameやpassword等の設定はしていないから?)

DB_HOST=127.0.0.1
DB_PORT=3306
DB_USERNAME=homestead
DB_PASSWORD=secret

また/home/username/projectname/config/database.phpに以下のような指定を行います。

'default' => env('DB_CONNECTION', 'sqlite'),

DBの設定に関しては以上の2ファイルをイジる必要があります。

最後にdatabaseディレクトリ配下にdatabase.sqliteというファイルを作成します。 touchでもsqlite3でもどっちのコマンドでも可です。

$ touch database.sqlite
$ sqlite3 database.sqlite
SQLite version 3.13.0 2016-05-18 10:57:30
Enter ".help" for usage hints.
sqlite> .tables
sqlite> .exit

ユーザー登録・認証機能

Laravelでユーザー登録・認証機能を作成する(たぶん)最も簡単な方法

ログイン中のユーザーの取得

use Illuminate\Support\Facades\Auth;

// 現在認証されているユーザーの取得
$user = Auth::user();

// 現在認証されているユーザーのID取得
$id = Auth::id();

ログイン中どうか判定

use Illuminate\Support\Facades\Auth;

if (Auth::check()) {
    // ユーザーはログインしている
}

ログの出力

まずFacadesをuseします。

use Illuminate\Support\Facades\Log;

後はメソッドを呼び出してやるだけでログが書き出されます。

Log::debug('log message')

参考: https://qiita.com/ak-ymst/items/00b41873be55e878eb84

クロスドメイン対応

LaravelでAPIを開発する際は、同一ドメインからのアクセスしか許可しないのであれば特に何もする必要がありませんが、クロスドメインからのアクセスを許可するならいくつか設定をしなければ以下のようなエラーが発生しAPIを叩くことが出来ません。

Failed to load https://[domain name]]/api/~~~~~~~~: No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin '~~~~~~~~~~~~~' is therefore not allowed access.

対策手順

middleware作成

$ php artisan make:middleware Cors

middlewareの編集

これでhttp://localhost:8000からのアクセスを許可したことになります。

<?php

namespace App\Http\Middleware;

use Illuminate\Support\Facades\Log;
use Closure;

class Cors
{
    /**
     * Handle an incoming request.
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  \Closure  $next
     * @return mixed
     */
    public function handle($request, Closure $next)
    {   
        $response = $next($request);

        $http_origin = isset($_SERVER['HTTP_ORIGIN']) ? $_SERVER['HTTP_ORIGIN'] : "";

        Log::debug("http_origin = " . $http_origin);
        if ($http_origin == "http://localhost:8000") {
            $response
                ->header("Access-Control-Allow-Origin" , $http_origin)
                ->header('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS');
        }

        return $response;
    }
}

Kernel.phpの編集

protected $middlewareGroups = [
    'api' => [
        \App\Http\Middleware\Cors::class,
        'throttle:60,1',
        'bindings',
    ],
];

Routeの編集

- Route::get("book", "bookController@index");
+ Route::match(["get", "options"], "book", "bookController@index");

axiosなどを使ってAPIにリクエストを送ると HTTPメソッドが options 形式になるので、API側のルートもoptionsを受け付けるように変更する必要があります。

以上でクロスドメイン対策は終了です。

CORS時にCookieを使用する

以下のようにサーバー、クライアントそれぞれでCredentialsを使用する設定を行う必要があります。

$response
    ->header('Access-Control-Allow-Credentials', 'true')
    ->header("Access-Control-Allow-Origin" , $http_origin)
    ->header('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS');
axios.defaults.withCredentials = true

もしくは

axios.create({
    baseURL: "http://127.0.0.1:8000/",
    withCredentials: true
})

参考

https://aloerina01.github.io/javascript/2016/10/13/1.html https://qiita.com/TsukasaGR/items/8f5713cee20d92f63c07

SwiftでUITextViewにリンクを挿入して、「プライバシーポリシーはこちら」を作る

はじめに

「プライバシーポリシー、利用規約に同意する」 みたいな会員登録画面などでよく見るリンク付きのUITextViewの設定方法についてです。 単純にURLを設定してそこに遷移する方法と、どのリンクがタップされたのか判定した上で遷移を行う方法をまとめてみました。

URLのリンクに遷移する

単純に設定したURLに遷移させる方法です。これでリンクになっている「こちら」をタップするとヤフーのトップページに遷移します。

スクリーンショット 2018-06-13 23.01.59.png

let textView: UITextView = UITextView(frame: CGRect(x: 0, y: 0, width: 300, height: 300))
textView.center = view.center
textView.backgroundColor = .clear
textView.font = UIFont.systemFont(ofSize: 17.0) // フォントの設定をする.
textView.textColor = .white // フォントの色の設定をする.
textView.textAlignment = .center
textView.dataDetectorTypes = .all // リンク、日付などを自動的に検出してリンクに変換する.
textView.isEditable = false // テキストを編集不可にする.

let style = NSMutableParagraphStyle()
style.alignment = .center
let attributedString = NSMutableAttributedString(string: "プライパシーポリシーはこちら")

attributedString.addAttributes([.foregroundColor: UIColor.white, .paragraphStyle: style, .font: UIFont.systemFont(ofSize: 17.0)], range: NSMakeRange(0, attributedString.length))

let range = attributedString.mutableString.range(of: "こちら")
attributedString.setAttributes([.underlineStyle : NSUnderlineStyle.styleSingle.rawValue, .link: URL(string: "https://www.yahoo.co.jp/")!, .foregroundColor: UIColor.white, .paragraphStyle: style,], range: range)

textView.attributedText = attributedString

どのリンクがタップされたのか判定する

単純にURLに遷移するだけなら上の方法でも良いですが、タップ時に何らかの処理をしたい場合やアプリ内の画面に遷移する場合は、UITextViewのデリゲートメソッド内でどのリンクがタップされたのか判別してハンドリングする必要があります。

extension ViewController: UITextViewDelegate {
    func textView(_ textView: UITextView, shouldInteractWith URL: URL, in characterRange: NSRange, interaction: UITextItemInteraction) -> Bool {
        let urlString = URL.absoluteString
        if urlString.contains("here") {
            print("こちらがタップされました")
            // 何かしらの処理
            return false
        }
        return true
     }
}

拡張関数を作ってみた

自分は以下のような拡張関数を定義して使ってます。 個人的にはこの程度の設定があれば十分なので、重宝してます。

extension UITextView {
    
    func drawUnderLineWithLink(text: String,
                               linedText: [String: URL],
                               color: UIColor = .white,
                               lineStyle: NSUnderlineStyle = .styleSingle,
                               alignment: NSTextAlignment = .center,
                               font: UIFont = UIFont.systemFont(ofSize: 17.0)) {
        
        let style = NSMutableParagraphStyle()
        style.alignment = alignment
        
        let attributedString = NSMutableAttributedString(string: text)
        
        attributedString.addAttributes([.foregroundColor: color,
                                        .paragraphStyle: style,
                                        .font: font],
                                        range: NSMakeRange(0, attributedString.length))
        
        linedText.forEach { (txt, url) in
            let range = attributedString.mutableString.range(of: txt)
            attributedString.setAttributes([.underlineStyle : lineStyle.rawValue,
                                            .link: url,
                                            .font: font], range: range)
        }
        self.attributedText = attributedString
    }

}

利用方法

let linedText: [String: URL] = ["続行": URL(string: "https://www.yahoo.co.jp/")!,
                                "アカウント作成": URL(string: "https://www.yahoo.co.jp/")!,
                                "もっと": URL(string: "https://www.yahoo.co.jp/")!]
textView.drawUnderLineWithLink(text: "続行, アカウント作成, もっとをタップしてください", linedText: linedText)

参考

https://qiita.com/shtnkgm/items/0009ef445a96126d7b16 https://qiita.com/shtnkgm/items/3c8b6b794219fbf087ba