[Golang]ローカルサーバーを立てた～ファイルアップロード～

初めに
===
GoでWebServerを立てて、Androidアプリからファイルのアップロードを行う

環境
===
PC Windows10
Android Studio 4.0
Kotlin 1.3.72
Android端末　Emulator(API Level 29)
Go 1.11.1

構成図
===
![go_server.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/487320/a511ca97-5de7-2c52-aa2a-61d690e74dee.png)
Androidアプリからファイルを送信して、サーバー側で保存、サーバーからアプリにステータスコードを返却する

実装
===
ローカルサーバー
--

```Server.go
package controllers

import (
	"fmt"
	"net/http"
	"io/ioutil"
	"os"
)

func apiSampleHandler(w http.ResponseWriter, r *http.Request) {
	switch(r.Method) {
		case "POST":
			fmt.Println("POST")
            // アップロードしたファイルとヘッダーを取得 
			file, fileHeader, _ := r.FormFile ("upload_file")　
			fileName := fileHeader.Filename // ヘッダーからファイル名を取得
			defer file.Close()              // ファイルを最後にClose
			data, _ := ioutil.ReadAll(file) // ファイルを読み出し
			saveFile, _ := os.Create(fileName) // 保存用のファイルを生成
			defer saveFile.Close()          // 保存用のファイルを最後にClose
			_, err = saveFile.Write(data)   // ファイルに書き込み
			if err != nil {
				fmt.Println("can not write file")
				w.WriteHeader(http.StatusBadRequest)
				return
			}
		case "GET":
			fmt.Println("GET")
	}
    // 200:OKを返す 
	w.WriteHeader(http.StatusOK)
}

// main.goから呼び出してサーバーを起動
func StartWebServer() error {
	http.HandleFunc("/", apiSampleHandler)
	return http.ListenAndServe(":8080", nil)
}
```
main.goを実行し、サーバーを起動
http://localhost:8080 にアクセスして、サーバーが起動していることを確認

Android アプリ
--
1．Androidmanifest.xml
-
インターネットにアクセスするためのPermissionを定義

```AndroidManifest.xml
<uses-permission android:name="android.permission.INTERNET" />
```

Android 9.0からhttpの通信の設定がOFFになるので```usesCleartextTrafic```をtrueに設定

```AndroidManifest.xml
<application
     省略
    android:usesCleartextTraffic="true">
    <activity android:name=".MainActivity">
        省略
    </activity>
</application>
```

2．ファイルを生成
-
Android端末のアプリ固有領域（```/data/data/{package_name}/files```配下）にテキストファイルを生成

```File.kt
const val FILE_EXPAND = ".txt"

class File {
    fun makeTxtFile(context: Context, fileName: String, str: String) {
        try {
            context.openFileOutput(fileName + FILE_EXPAND, Context.MODE_PRIVATE).use {
                it.write(str.toByteArray())
            }
        } catch (e: IOException) {
            Log.e("File", "#makeTxtFile $e")
        }
    }
}
```
ファイルの生成を確認
[Device File Explorer でデバイス上のファイルを表示する](https://developer.android.com/studio/debug/device-file-explorer?hl=ja
)

3．サーバーアクセス
-
ファイルを読み込んで、http通信でファイルを送信する

```Net.kt
 fun startConnection(context: Context, requestUrl: String, requestMethod: String, fileName: String): Pair<Int, String> {
        val url = URL(requestUrl)                      // URLオブジェクト生成
        // UrlConnectionオブジェクト生成
        val urlConnection = url.openConnection() as HttpURLConnection 
        var result = ""
        var responseCode = 0
        try {
            val boundary = "--------------------------" 
            urlConnection.requestMethod = requestMethod // POST, GETなど
            urlConnection.doOutput = true               // リクエストボディの送信
            urlConnection.doInput = true                // レスポンスボディの受信
            urlConnection.useCaches = false             // キャッシュの利用
            // multipart/form-data：複数のデータを送信、boundary：複数データ間の区切り
            urlConnection.setRequestProperty(
                "Content-Type",
                "multipart/form-data; boundary=$boundary" 
            )
            val filePath = context.filesDir             // アプリ固有領域のパス
            val file = File("$filePath/$fileName$FILE_EXPAND") // ファイルオブジェクトを生成
            FileInputStream(file).use { fileInputStream ->
                urlConnection.connect()                 // コネクションを確立
                DataOutputStream(urlConnection.outputStream).use {
                   it.writeBytes(                       // headerを設定
                       TWO_HYPHEN + boundary + LINE_END +
                               "Content-Disposition: form-data; name=\"upload_file\"; " +
                               "filename=\"$fileName$FILE_EXPAND\"$LINE_END" +
                               "Content-Type: application/octet-stream$LINE_END$LINE_END"
                   )
                   // ファイル書き込み
                   val buffer = ByteArray(BUFFER_SIZE)
                   var bytesRead: Int
                   do {
                       bytesRead = fileInputStream.read(buffer)
                       if (bytesRead == -1) break
                       it.write(buffer, 0, bytesRead)
                   } while (true)
                   it.writeBytes(                       // footerの設定
                        LINE_END + TWO_HYPHEN + boundary + TWO_HYPHEN + LINE_END
                   )
                   it.flush()
                }
            }
            responseCode = urlConnection.responseCode   // レスポンスコードを取得
 
            BufferedReader(InputStreamReader(urlConnection.inputStream)).use {
                val sb = StringBuffer()
                for (line in it.readLines()) {
                    line.let { sb.append(line) }
                }
                result = sb.toString()
            }
        } catch (e: Exception) {
            Log.e("Net", "#startConnection$e")
        } finally {
            urlConnection.disconnect()
        }
        return responseCode to result
    }
}
```

4．IPアドレスの確認
-
アクセスしたいサーバー（PC）のIPアドレスをcommand promptで以下を実行し確認

```
>ipconfig
```
5．PCとAndroid端末を同一のネットワーク環境に接続
-
6．Androidにて接続
-
Net#startConnectionのrequestUrlにhttp://{確認したIPアドレス}:8080を設定してアプリを実行

ファイルのアップロードを確認
===
PCにてmain.goと同じディレクトリを確認する
ファイルが作成されていれば完了

最後に
===
全体ソースは[こちら](https://github.com/kurramkurram/GoServer)
アクセス先やRequestMethod、ファイル名はアプリで動的に変更できるようにしています。

参考文献
===
Goサーバー側
-
[Go言語でのファイルアップロード](https://qiita.com/yayoshM/items/c55254788b87541cad3b)

Androidアプリ側
-
[[Android] アプリ内にファイルを保存する FileOutputStream, FileInputStream](https://akira-watson.com/android/fileoutputstream.html)
[Upload File To Server - Android Example](https://androidexample.com/Upload_File_To_Server_-_Android_Example/index.php?view=article_discription&aid=83)
[[Android]kotlinでのwhile-FileInputStream.readエラー](https://reftec.work/posts/2019/11/140/)
[【Kotlin】複数の値を返したい！](https://qiita.com/sdkei/items/2d5dab51b53975286945)