これまでこのブログで取り上げてきたサンプルプログラム内では、Azure App ServiceからAzure Functionsを呼び出す際に利用するRestTemplateを用いたAPI通信で、JSON文字列のString型で通信を行ってきたが、オブジェクトのままで通信を行うこともできる。
今回は、Azure App ServiceからAzure Functionsを呼び出す際のAPI通信で、オブジェクトのままで通信を行ってみたので、そのサンプルプログラムを共有する。
前提条件
下記記事の実装が完了していること。
作成したサンプルプログラム(共通クラス)の内容
作成したサンプルプログラム(共通クラス)の構成は以下の通り。
なお、上記の赤枠は、前提条件のプログラムから追加・変更したプログラムである。
pom.xml(追加分)の内容は以下の通りで、JsonSerializeアノテーションを利用するためのjackson-databindを追加している。
1 2 3 4 5 6 | <!-- JsonSerializeアノテーションを利用するための設定 --> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> <version>2.13.0</version> </dependency> |
また、ファイルダウンロードリストの戻り値に利用するFileDataクラスの内容は以下の通りで、RestTemplateを用いたAPI通信でオブジェクトのままで通信を行えるよう、byte配列をByte配列に変更している。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | package com.example.model; import lombok.Data; @Data public class FileData { /** ID */ private int id; /** ファイル名 */ private String fileName; /** ファイルパス */ private String filePath; /** ファイルデータ */ private Byte[] fileData; } |
さらに、ファイルアップロード時に利用するFileUploadParamクラスの内容は以下の通りで、ファイル名とByte配列をもつファイルデータを設定するようにしている。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | package com.example.model; import lombok.Data; @Data public class FileUploadParam { /** ファイル名 */ private String fileName; /** ファイルデータ */ private Byte[] fileData; } |
また、ファイルダウンロードリストを取得する際のGetFileListParamクラスの内容は以下の通りで、RestTemplateを用いたAPI通信でオブジェクトのままで通信を行えるよう、@JsonSerializeアノテーションを付与している。
1 2 3 4 5 6 7 8 9 10 11 | package com.example.model; import com.fasterxml.jackson.databind.annotation.JsonSerialize; import lombok.Data; @Data @JsonSerialize public class GetFileListParam { } |
なお、@JsonSerializeアノテーションを付与しないと、「com.fasterxml.jackson.databind.exc.InvalidDefinitionException: No serializer found for class com.example.model.GetFileListParam and no properties discovered to create BeanSerializer」というエラーが出るため、オブジェクトをJSON文字列に変換するための@JsonSerializeアノテーションが必要になる。
その他のソースコード内容は、以下のサイトを参照のこと。
https://github.com/purin-it/azure/tree/master/azure-rest-template-object/demoAzureCommon
作成したサンプルプログラム(App Service側)の内容
作成したサンプルプログラム(App Service側)の構成は以下の通り。
なお、上記の赤枠は、前提条件のプログラムから変更したプログラムである。
pom.xml(追加分)の内容は以下の通りで、ArrayUtilsクラスを含むcommons-lang3の設定を追加している。
1 2 3 4 5 | <!-- ArrayUtilsクラスを含むcommons-lang3の設定 --> <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-lang3</artifactId> </dependency> |
また、コントローラクラスの内容は以下の通りで、HttpEntityクラスにParamクラスのオブジェクトを設定し、RestTemplateを用いたAPI通信の戻り値をResultクラスに変更している。また、ファイルデータがbyte配列からByte配列に変わったため、ファイルデータの設定/取り出しを、ArrayUtilsクラスのメソッドを用いるよう変更している。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 | package com.example.demo; import java.io.OutputStream; import java.net.URLEncoder; import java.util.ArrayList; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; import org.apache.commons.io.IOUtils; import org.apache.commons.lang3.ArrayUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.http.HttpEntity; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.client.RestTemplate; import org.springframework.web.multipart.MultipartFile; import org.springframework.web.servlet.mvc.support.RedirectAttributes; import org.thymeleaf.util.StringUtils; import com.example.model.FileData; import com.example.model.FileUploadParam; import com.example.model.FileUploadResult; import com.example.model.GetFileListParam; import com.example.model.GetFileListResult; @Controller public class DemoController { /** RestTemplateオブジェクト */ @Autowired private RestTemplate restTemplate; /** application.propertiesからdemoAzureFunc.urlBaseの値を取得 */ @Value("${demoAzureFunc.urlBase}") private String demoAzureFuncBase; /** * ファイルリストを取得し、メイン画面を初期表示する. * @param model Modelオブジェクト * @param session HttpSessionオブジェクト * @return メイン画面 */ @GetMapping("/") public String index(Model model, HttpSession session) { // Azure FunctionsのgetFileList関数を呼び出すためのヘッダー情報を設定する HttpHeaders headers = new HttpHeaders(); headers.setContentType(MediaType.APPLICATION_JSON); // Azure FunctionsのgetFileList関数を呼び出すための引数を設定する HttpEntity<GetFileListParam> entity = new HttpEntity<>(new GetFileListParam(), headers); // Azure FunctionsのgetFileList関数を呼び出す ResponseEntity<GetFileListResult> response = restTemplate.exchange( demoAzureFuncBase + "getFileList", HttpMethod.POST , new HttpEntity<>(entity), GetFileListResult.class); GetFileListResult getFileListResult = response.getBody(); model.addAttribute("fileDataList", getFileListResult.getFileDataList()); session.setAttribute("fileDataList", getFileListResult.getFileDataList()); model.addAttribute("message" , "アップロードするファイルを指定し、アップロードボタンを押下してください。"); return "main"; } /** * ファイルダウンロード処理. * @param id ID * @param response HttpServletResponse * @param session HttpSessionオブジェクト * @return 画面遷移先(nullを返す) */ @RequestMapping("/download") public String download(@RequestParam("id") String id , HttpServletResponse response, HttpSession session) { // セッションからファイルリストを取得する @SuppressWarnings("unchecked") ArrayList<FileData> fileDataList = (ArrayList<FileData>) session.getAttribute("fileDataList"); FileData fileData = fileDataList.get(Integer.parseInt(id) - 1); // ファイルダウンロードの設定を実施 // ファイルの種類は指定しない response.setContentType("application/octet-stream"); response.setHeader("Cache-Control", "private"); response.setHeader("Pragma", ""); response.setHeader("Content-Disposition", "attachment;filename=\"" + getFileNameEncoded(fileData.getFileName()) + "\""); // ダウンロードファイルへ出力 try (OutputStream out = response.getOutputStream()) { out.write(ArrayUtils.toPrimitive(fileData.getFileData())); out.flush(); } catch (Exception e) { System.err.println(e); } // 画面遷移先はnullを指定 return null; } /** * ファイルデータをAzure Blob Storageに登録する. * @param uploadFile アップロードファイル * @param model Modelオブジェクト * @param redirectAttributes リダイレクト先に渡すパラメータ * @return メイン画面 */ @PostMapping("/upload") public String add(@RequestParam("upload_file") MultipartFile uploadFile , Model model, RedirectAttributes redirectAttributes) { // ファイルが未指定の場合はエラーとする if (uploadFile == null || StringUtils.isEmptyOrWhitespace( uploadFile.getOriginalFilename())) { redirectAttributes.addFlashAttribute("errMessage" , "ファイルを指定してください。"); return "redirect:/"; } // Azure FunctionsのfileUpload関数を呼び出すためのヘッダー情報を設定する HttpHeaders headers = new HttpHeaders(); headers.setContentType(MediaType.APPLICATION_JSON); // Azure FunctionsのfileUpload関数を呼び出すための引数を設定する FileUploadParam fileUploadParam = new FileUploadParam(); try { fileUploadParam.setFileName(uploadFile.getOriginalFilename()); fileUploadParam.setFileData(ArrayUtils.toObject( IOUtils.toByteArray(uploadFile.getInputStream()))); } catch (Exception ex) { throw new RuntimeException(ex); } HttpEntity<FileUploadParam> entity = new HttpEntity<>(fileUploadParam, headers); // Azure FunctionsのfileUpload関数を呼び出す ResponseEntity<FileUploadResult> response = restTemplate.exchange( demoAzureFuncBase + "fileUpload", HttpMethod.POST , entity, FileUploadResult.class); FileUploadResult fileUploadResult = response.getBody(); // メイン画面へ遷移 model.addAttribute("message", fileUploadResult.getMessage()); return "redirect:/"; } /** * ファイル名をUTF-8でエンコードする. * @param filePath ファイル名 * @return エンコード後のファイル名 */ private String getFileNameEncoded(String fileName) { String fileNameAft = ""; if (!StringUtils.isEmptyOrWhitespace(fileName)) { try { // ファイル名をUTF-8でエンコードして返却 fileNameAft = URLEncoder.encode(fileName, "UTF-8"); } catch (Exception e) { System.err.println(e); return ""; } } return fileNameAft; } } |
その他のソースコード内容は、以下のサイトを参照のこと。
https://github.com/purin-it/azure/tree/master/azure-rest-template-object/demoAzureApp
作成したサンプルプログラム(Azure Functions側)の内容
作成したサンプルプログラム(Azure Functions側)の構成は以下の通り。
なお、上記の赤枠は、前提条件のプログラムから変更したプログラムである。
pom.xml(追加分)の内容は以下の通りで、ArrayUtilsクラスを含むcommons-lang3の設定を追加している。
1 2 3 4 5 | <!-- ArrayUtilsクラスを含むcommons-lang3の設定 --> <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-lang3</artifactId> </dependency> |
また、ハンドラークラスの内容はそれぞれ以下の通りで、@HttpTriggerアノテーションでParamクラスのHttpRequestMessageを受け取るようにしている。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 | package com.example; import java.util.Optional; import org.springframework.cloud.function.adapter.azure.FunctionInvoker; import org.springframework.http.MediaType; import com.example.model.FileUploadParam; import com.example.model.FileUploadResult; import com.microsoft.azure.functions.ExecutionContext; import com.microsoft.azure.functions.HttpMethod; import com.microsoft.azure.functions.HttpRequestMessage; import com.microsoft.azure.functions.HttpResponseMessage; import com.microsoft.azure.functions.HttpStatus; import com.microsoft.azure.functions.annotation.AuthorizationLevel; import com.microsoft.azure.functions.annotation.FunctionName; import com.microsoft.azure.functions.annotation.HttpTrigger; public class FileUploadHandler extends FunctionInvoker<FileUploadParam, FileUploadResult> { /** * HTTP要求に応じて、DemoAzureFunctionクラスのfileUploadメソッドを呼び出し、 * その戻り値をボディに設定したレスポンスを返す. * @param request リクエストオブジェクト * @param context コンテキストオブジェクト * @return レスポンスオブジェクト */ @FunctionName("fileUpload") public HttpResponseMessage execute( @HttpTrigger(name = "request" , methods = HttpMethod.POST , authLevel = AuthorizationLevel.ANONYMOUS) HttpRequestMessage<Optional<FileUploadParam>> request, ExecutionContext context) { // リクエストオブジェクトからFileUploadParamオブジェクトを取得する FileUploadParam fileUploadParam = request.getBody().get(); // handleRequestメソッド内でDemoAzureFunctionクラスのfileUploadメソッドを呼び出し、 // その戻り値をボディに設定したレスポンスを、JSON形式で返す return request.createResponseBuilder(HttpStatus.OK) .body(handleRequest(fileUploadParam, context)) .header("Content-Type", MediaType.APPLICATION_JSON_VALUE) .build(); } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 | package com.example; import java.util.Optional; import org.springframework.cloud.function.adapter.azure.FunctionInvoker; import org.springframework.http.MediaType; import com.example.model.GetFileListParam; import com.example.model.GetFileListResult; import com.microsoft.azure.functions.ExecutionContext; import com.microsoft.azure.functions.HttpMethod; import com.microsoft.azure.functions.HttpRequestMessage; import com.microsoft.azure.functions.HttpResponseMessage; import com.microsoft.azure.functions.HttpStatus; import com.microsoft.azure.functions.annotation.AuthorizationLevel; import com.microsoft.azure.functions.annotation.FunctionName; import com.microsoft.azure.functions.annotation.HttpTrigger; public class GetFileListHandler extends FunctionInvoker<GetFileListParam, GetFileListResult> { /** * HTTP要求に応じて、DemoAzureFunctionクラスのfileUploadメソッドを呼び出し、 * その戻り値をボディに設定したレスポンスを返す. * @param request リクエストオブジェクト * @param context コンテキストオブジェクト * @return レスポンスオブジェクト */ @FunctionName("getFileList") public HttpResponseMessage execute( @HttpTrigger(name = "request" , methods = HttpMethod.POST , authLevel = AuthorizationLevel.ANONYMOUS) HttpRequestMessage<Optional<GetFileListParam>> request, ExecutionContext context) { // リクエストオブジェクトからFileUploadParamオブジェクトを取得する GetFileListParam getFileListParam = request.getBody().get(); // handleRequestメソッド内でDemoAzureFunctionクラスのfileUploadメソッドを呼び出し、 // その戻り値をボディに設定したレスポンスを、JSON形式で返す return request.createResponseBuilder(HttpStatus.OK) .body(handleRequest(getFileListParam, context)) .header("Content-Type", MediaType.APPLICATION_JSON_VALUE) .build(); } } |
さらに、サービスクラスの内容はそれぞれ以下の通りで、ファイルデータがbyte配列からByte配列に変わったため、ファイルデータの設定/取り出しを、ArrayUtilsクラスのメソッドを用いるよう変更している。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 | package com.example.service; import org.apache.commons.lang3.ArrayUtils; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; import com.example.model.FileUploadParam; import com.example.model.FileUploadResult; import com.microsoft.azure.storage.CloudStorageAccount; import com.microsoft.azure.storage.blob.BlobOutputStream; import com.microsoft.azure.storage.blob.CloudBlobClient; import com.microsoft.azure.storage.blob.CloudBlobContainer; import com.microsoft.azure.storage.blob.CloudBlockBlob; @Service public class FileUploadService { /** Azure Storageのアカウント名 */ @Value("${azure.storage.accountName}") private String storageAccountName; /** Azure Storageへのアクセスキー */ @Value("${azure.storage.accessKey}") private String storageAccessKey; /** Azure StorageのBlobコンテナー名 */ @Value("${azure.storage.containerName}") private String storageContainerName; /** * ファイルアップロード処理を行うサービス. * @param fileUploadParam ファイルアップロード用Param * @return ファイルアップロードサービスクラスの呼出結果 */ public FileUploadResult fileUpload(FileUploadParam fileUploadParam) { // ファイルアップロード処理 try { // Blobストレージへの接続文字列 String storageConnectionString = "DefaultEndpointsProtocol=https;" + "AccountName=" + storageAccountName + ";" + "AccountKey=" + storageAccessKey + ";"; // ストレージアカウントオブジェクトを取得 CloudStorageAccount storageAccount = CloudStorageAccount.parse(storageConnectionString); // Blobクライアントオブジェクトを取得 CloudBlobClient blobClient = storageAccount.createCloudBlobClient(); // Blob内のコンテナーを取得 CloudBlobContainer container = blobClient.getContainerReference(storageContainerName); // Blob内のコンテナーにデータを書き込む CloudBlockBlob blob = container.getBlockBlobReference( fileUploadParam.getFileName()); BlobOutputStream output = blob.openOutputStream(); output.write(ArrayUtils.toPrimitive(fileUploadParam.getFileData())); output.close(); } catch (Exception ex) { throw new RuntimeException(ex); } FileUploadResult result = new FileUploadResult(); result.setMessage("ファイルアップロードが完了しました。"); return result; } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 | package com.example.service; import java.io.InputStream; import java.util.ArrayList; import org.apache.commons.io.IOUtils; import org.apache.commons.lang3.ArrayUtils; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; import com.example.model.FileData; import com.example.model.GetFileListParam; import com.example.model.GetFileListResult; import com.microsoft.azure.storage.CloudStorageAccount; import com.microsoft.azure.storage.blob.CloudBlobClient; import com.microsoft.azure.storage.blob.CloudBlobContainer; import com.microsoft.azure.storage.blob.CloudBlockBlob; import com.microsoft.azure.storage.blob.ListBlobItem; @Service public class GetFileListService { /** Azure Storageのアカウント名 */ @Value("${azure.storage.accountName}") private String storageAccountName; /** Azure Storageへのアクセスキー */ @Value("${azure.storage.accessKey}") private String storageAccessKey; /** Azure StorageのBlobコンテナー名 */ @Value("${azure.storage.containerName}") private String storageContainerName; /** * ファイルリスト取得処理を行うサービス. * @param getFileListParam ファイル取得用Param * @return ファイルリスト取得サービスクラスの呼出結果 */ public GetFileListResult getFileList(GetFileListParam getFileListParam) { GetFileListResult result = new GetFileListResult(); // ファイルアップロード処理 try { // Blobストレージへの接続文字列 String storageConnectionString = "DefaultEndpointsProtocol=https;" + "AccountName=" + storageAccountName + ";" + "AccountKey=" + storageAccessKey + ";"; // ストレージアカウントオブジェクトを取得 CloudStorageAccount storageAccount = CloudStorageAccount.parse(storageConnectionString); // Blobクライアントオブジェクトを取得 CloudBlobClient blobClient = storageAccount.createCloudBlobClient(); // Blob内のコンテナーを取得 CloudBlobContainer container = blobClient.getContainerReference(storageContainerName); // Blob情報を取得し、戻り値に設定する ArrayList<FileData> fileDataList = new ArrayList<>(); int index = 1; for (ListBlobItem blobItem : container.listBlobs()) { // ファイル名・ファイルパスを取得 String filePath = blobItem.getUri().toString(); String fileName = filePath.substring(filePath.lastIndexOf("/") + 1); // Blobデータを読み込み CloudBlockBlob blob = container.getBlockBlobReference(fileName); InputStream input = blob.openInputStream(); // ファイルデータを設定 FileData fileData = new FileData(); fileData.setId(index); fileData.setFileName(fileName); fileData.setFilePath(filePath); fileData.setFileData(ArrayUtils.toObject(IOUtils.toByteArray(input))); fileDataList.add(fileData); index++; } result.setFileDataList(fileDataList); } catch (Exception ex) { throw new RuntimeException(ex); } return result; } } |
その他のソースコード内容は、以下のサイトを参照のこと。
https://github.com/purin-it/azure/tree/master/azure-rest-template-object/demoAzureFunc
サンプルプログラムの実行結果
サンプルプログラムの実行結果は、以下の各記事の「作成したサンプルプログラムの実行結果」と同じになる。
要点まとめ
- Azure App ServiceからAzure Functionsを呼び出す際に利用するRestTemplateを用いたAPI通信は、オブジェクトのままで通信を行うこともできる。その際、byte配列の項目があればByte配列に変更し、空のParamオブジェクトがあれば@JsonSerializeアノテーションを付与する必要がある。
- byte配列⇔Byte配列の変換を行うには、Apache Commons Lang 3内のArrayUtilsクラスのメソッドを用いればよい。