これまでこのブログで取り上げてきたサンプルプログラム内では、Azure App ServiceからAzure Functionsを呼び出す際に利用するRestTemplateを用いたAPI通信で、JSON文字列のString型で通信を行ってきたが、オブジェクトのままで通信を行うこともできる。
今回は、Azure App ServiceからAzure Functionsを呼び出す際のAPI通信で、オブジェクトのままで通信を行ってみたので、そのサンプルプログラムを共有する。
前提条件
下記記事の実装が完了していること。
作成したサンプルプログラム(共通クラス)の内容
作成したサンプルプログラム(共通クラス)の構成は以下の通り。
なお、上記の赤枠は、前提条件のプログラムから追加・変更したプログラムである。
pom.xml(追加分)の内容は以下の通りで、JsonSerializeアノテーションを利用するためのjackson-databindを追加している。
<!-- JsonSerializeアノテーションを利用するための設定 --> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> <version>2.13.0</version> </dependency>
また、ファイルダウンロードリストの戻り値に利用するFileDataクラスの内容は以下の通りで、RestTemplateを用いたAPI通信でオブジェクトのままで通信を行えるよう、byte配列をByte配列に変更している。
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配列をもつファイルデータを設定するようにしている。
package com.example.model; import lombok.Data; @Data public class FileUploadParam { /** ファイル名 */ private String fileName; /** ファイルデータ */ private Byte[] fileData; }
また、ファイルダウンロードリストを取得する際のGetFileListParamクラスの内容は以下の通りで、RestTemplateを用いたAPI通信でオブジェクトのままで通信を行えるよう、@JsonSerializeアノテーションを付与している。
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の設定を追加している。
<!-- ArrayUtilsクラスを含むcommons-lang3の設定 --> <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-lang3</artifactId> </dependency>
また、コントローラクラスの内容は以下の通りで、HttpEntityクラスにParamクラスのオブジェクトを設定し、RestTemplateを用いたAPI通信の戻り値をResultクラスに変更している。また、ファイルデータがbyte配列からByte配列に変わったため、ファイルデータの設定/取り出しを、ArrayUtilsクラスのメソッドを用いるよう変更している。
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の設定を追加している。
<!-- ArrayUtilsクラスを含むcommons-lang3の設定 --> <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-lang3</artifactId> </dependency>
また、ハンドラークラスの内容はそれぞれ以下の通りで、@HttpTriggerアノテーションでParamクラスのHttpRequestMessageを受け取るようにしている。
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(); } }
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クラスのメソッドを用いるよう変更している。
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; } }
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クラスのメソッドを用いればよい。