이 섹션에서는 Windows용 FRE 12 애플리케이션을 Azure Cloud Service에 배포하는 방법을 설명합니다. 예제로는 Azure Storage account의 컨테이너에 있는 파일을 처리하는 Worker를 소개합니다.이 시나리오를 구현하려면 *.abbyy.com 라이선스 서버에 연결되는 온라인 라이선스를 사용합니다.
ABBYY Licensing Service는 한 번에 하나의 온라인 라이선스만 사용할 수 있습니다.
사전 준비 단계는 로컬 컴퓨터에서 수행해야 합니다. 이 단계를 완료하면 애플리케이션 배포를 시작하는 데 필요한 모든 설정과 파일이 준비됩니다.
ABBYY FineReader Engine 라이브러리와 Licensing Service용 아카이브 두 개를 만듭니다(예: LibraryPackage.zip 및 LSPackage.zip). FREngineDistribution.csv 파일을 사용하면 파일 목록을 자동으로 생성할 수 있습니다. ABBYY FineReader Engine과 라이선스 서버는 동일한 패키지의 것을 사용해야 합니다. 그렇지 않으면 호환성이 보장되지 않습니다.
Azure Storage 계정(이 문서에서는 frestorage)을 만듭니다. 필요한 지침은 모두 Azure 웹사이트에서 확인할 수 있습니다.
frestorage 안에 Blob 컨테이너 세 개를 만듭니다:
fre-lib - ABBYY FineReader Engine 파일용
fre-input - 입력 파일용
fre-output - 처리 결과용
가장 편한 방법(.NET, Powershell, Python 스크립트 또는 Azure Storage Explorer/Azure Portal 애플리케이션 사용)으로 LibraryPackage.zip 및 LSPackage.zip을 fre-lib 컨테이너에 업로드합니다.
예시로는 구성된 환경에서 작업하기 위해 WorkerRole 프로젝트를 사용합니다. 필요한 모든 구성 파일(.csdef 및 Cloud.cscfg)은 프로젝트를 생성하면 자동으로 만들어집니다.
fre-input 컨테이너의 파일을 처리하고 처리 결과를 fre-output 컨테이너에 게시하도록 WorkerRole 프로젝트를 구현합니다.
Cloud Service가 작동할 수 있도록 준비하는 OnStart 메서드. 이 메서드는 엔진을 초기화하고 TLS 프로토콜을 설정하는 데 사용됩니다.
fre-input 컨테이너에서 가져온 파일을 주기적으로 처리하는 RunAsync 메서드. 이 메서드는 fre-input 컨테이너의 파일을 감지하고, 이를 메모리에서 처리한 다음 fre-output 컨테이너에 저장합니다(Processor.cs, IFileWriter.cs, Config.cs 참조).
Cloud Service 작업을 종료하는 OnStop 메서드. 이 메서드는 엔진을 종료하는 데 사용됩니다.
PrepareLibrary - fre-lib 컨테이너에서 ABBYY FineReader Engine 라이브러리를 가져와 로컬 저장소에 압축을 풉니다(자세한 내용은 아래 PrepareLibrary.cmd 및 PrepareLibrary.ps1 파일을 참조하세요).
PrepareLS - fre-lib 컨테이너에서 Licensing Service를 가져와 압축을 풀고 실행합니다(자세한 내용은 아래 PrepareLS.cmd 및 PrepareLS.ps1 파일을 참조하세요).
위에 나열된 스크립트는 애플리케이션을 시작하기 전에 제안된 순서대로 정확히 실행해야 합니다. 스크립트 실행 순서를 사용자 지정하려면 다음 속성을 지정하세요.
taskType=“simple” - 작업이 한 번에 하나씩 동기적으로 실행됩니다.
executionContext=“elevated” - 관리자 권한으로 시작 스크립트를 실행합니다(애플리케이션 설치 및 LicensingService.exe 실행에 필요).
그 결과 Cloud Service가 관리할 수 있는 폴더가 생성됩니다. 이 폴더는 위에 나열된 스크립트가 ABBYY FineReader Engine을 업로드하고, 이후 샘플에서 사용할 FREngine.dll을 로드하는 데 사용됩니다.
CleanUpOnStart.cmd
rem 이전 ABBYY FineReader Engine 및 Licensing Service 패키지를 정리하는 스크립트echo Cleaning up before launching service >> ".\CleanUpOnStart.log" 2>&1PowerShell -ExecutionPolicy Unrestricted .\StartupScripts\CleanUpOnStart.ps1 >> ".\CleanUpOnStart.log" 2>&1echo Cleaning up beafore launching service. >> ".\CleanUpOnStart.log" 2>&1exit /B %errorlevel%
CleanUpOnStart.ps1
Write-Host "CleanUpOnStart.ps1 started...";# 로컬 스토리지 경로(ServiceDefinition.csdef 참조)$local_storage_name = "LocalStorage1";[System.Reflection.Assembly]::LoadWithPartialName("Microsoft.WindowsAzure.ServiceRuntime");$local_storage_path = ([Microsoft.WindowsAzure.ServiceRuntime.RoleEnvironment]::GetLocalResource($local_storage_name)).RootPath.TrimEnd('\\');# 실행 중인 Licensing Service 프로세스가 있으면 중지$processes = Get-Process;foreach( $process in $processes ) { if( $process.Name -eq "LicesingService" ) { Stop-Process $process; }}# fre_packages 폴더 정리 후 다시 생성$destination_path = Join-Path -Path $local_storage_path -ChildPath 'fre_packages';if( Test-Path -Path $destination_path ) { Remove-Item $destination_path -Recurse -Force;}New-Item -ItemType Directory -Path $destination_path;# Input 폴더 정리 후 다시 생성$destination_path = Join-Path -Path $local_storage_path -ChildPath 'Input';if( Test-Path -Path $destination_path ) { Remove-Item $destination_path -Recurse -Force;}New-Item -ItemType Directory -Path $destination_path;# Results 폴더 정리 후 다시 생성$destination_path = Join-Path -Path $local_storage_path -ChildPath 'Results';if( Test-Path -Path $destination_path ) { Remove-Item $destination_path -Recurse -Force;}New-Item -ItemType Directory -Path $destination_path;Write-Host ("Resource folder was cleaned up.");
PreparePoShModules.cmd
rem 스토리지 계정에 연결하고 ABBYY FineReader Engine 및 Licensing Service 패키지를 다운로드할 수 있도록 모듈을 준비하는 스크립트echo Preparing PoSh Modules >> ".\PreparePoShModules.log" 2>&1PowerShell -ExecutionPolicy Unrestricted .\StartupScripts\PreparePoShModules.ps1 >> ".\PreparePoShModules.log" 2>&1echo PoSh Modules wer prepared. >> ".\PreparePoShModules.log" 2>&1exit /B %errorlevel%
rem Licensing Service 패키지를 준비하고 LicensingService.exe를 시작하는 스크립트echo Preparing LS >> ".\PrepareLS.log" 2>&1PowerShell -ExecutionPolicy Unrestricted .\StartupScripts\PrepareLS.ps1 >> ".\PrepareLS.log" 2>&1echo LS was prepared. >> ".\PrepareLS.log" 2>&1exit /B %errorlevel%
namespace WorkerRole1.Engine{ public class FileWriter : FREngine.IFileWriter, IDisposable { public FileWriter(string _resultBlobName, string _fileExtension) { resultBlobName = _resultBlobName; fileExtension = _fileExtension; } public void Open(string fileName, ref int bufferSize) { stream = new MemoryStream(); } public void Write(byte[] data) { stream.Write(data, 0, data.Length); } public void Close() { // <OutputContainerName>의 새 blob에 대한 연결 생성 // 처리 결과는 여기에 저장됩니다 BlobClient resultBlobClient = new BlobClient(Config.ConnectionString, Config.OutputContainerName, resultBlobName + fileExtension); // 기존 파일 덮어쓰기 resultBlobClient.DeleteIfExists(); // 처음부터 파일을 쓰기 위해 위치를 0으로 설정 stream.Position = 0; resultBlobClient.Upload(stream); stream.Close(); } public void Dispose() { // 데이터가 기록된 후에도 액세스할 수 있도록 Dispose 시 memory stream 닫기 stream.Close(); } private string resultBlobName; private string fileExtension; private MemoryStream stream; }}
ABBYY FineReader Engine를 사용한 파일 처리 샘플
namespace WorkerRole1{ public class WorkerRole : RoleEntryPoint { private readonly CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); private readonly ManualResetEvent runCompleteEvent = new ManualResetEvent(false); Processor processor = new Processor(); public override void Run() { Trace.TraceInformation("WorkerRole1 is running"); try { this.RunAsync(this.cancellationTokenSource.Token).Wait(); } finally { this.runCompleteEvent.Set(); } } public override bool OnStart() { // 최대 동시 연결 수를 설정합니다 ServicePointManager.DefaultConnectionLimit = 12; // 스토리지 계정에 연결할 수 있도록 TLS를 1.2로 설정합니다 System.Net.ServicePointManager.SecurityProtocol = System.Net.SecurityProtocolType.Tls12; // 구성 변경 처리에 대한 자세한 내용은 // https://go.microsoft.com/fwlink/?LinkId=166357의 MSDN 항목을 참조하세요 bool result = base.OnStart(); Trace.TraceInformation("WorkerRole1 has been started"); processor.LoadEngine(); return result; } public override void OnStop() { processor.UnloadEngine(); Trace.TraceInformation("WorkerRole1 is stopping"); this.cancellationTokenSource.Cancel(); this.runCompleteEvent.WaitOne(); base.OnStop(); Trace.TraceInformation("WorkerRole1 has stopped"); } private async Task RunAsync(CancellationToken cancellationToken) { // TODO: 아래 코드를 사용자 고유의 로직으로 바꾸세요 while (!cancellationToken.IsCancellationRequested) { Trace.TraceInformation("Working"); try { // 컨테이너를 만들고 컨테이너 클라이언트 객체를 반환합니다 BlobContainerClient inputContainerClient = new BlobContainerClient(Config.ConnectionString, Config.InputContainerName); foreach (BlobItem blobItem in inputContainerClient.GetBlobs()) { Trace.TraceInformation("\t" + blobItem.Name); BlobClient blobClient = new BlobClient(Config.ConnectionString, Config.InputContainerName, blobItem.Name); Trace.TraceInformation("\t Downloading blob to memory: " + blobItem.Name); MemoryStream memoryStream = new MemoryStream(); blobClient.DownloadTo(memoryStream); Trace.TraceInformation("\t Processing in FRE: " + blobItem.Name); string resultBlobName = processor.ProcessImage(memoryStream, blobItem.Name); Trace.TraceInformation("\t Result blob name in output container: " + resultBlobName); Trace.TraceInformation("\t Deleting from input container: " + blobItem.Name); blobClient.Delete(); } } catch (Exception error) { Trace.TraceInformation("error: " + error.Message); } await Task.Delay(1000); } } }}
Processor.cs
namespace WorkerRole1{ class Processor : IDisposable { EngineLoader engineLoader = null; private void displayMessage(string text) { File.AppendAllText(".\\FRE.log", text + "\n"); Trace.TraceInformation("\t" + text ); } private void loadProfile() { engineLoader.Engine.LoadPredefinedProfile("DocumentConversion_Accuracy"); } private void setupFREngine() { displayMessage("Loading predefined profile..."); loadProfile(); } public void LoadEngine() { try { if (engineLoader == null) { // Hello sample에서와 동일한 EngineLoader engineLoader = new EngineLoader(); } setupFREngine(); } catch (Exception error) { displayMessage("error: " + error.Message); } } public void UnloadEngine() { try { if (engineLoader != null) { engineLoader.Dispose(); engineLoader = null; } } catch (Exception error) { displayMessage("error: " + error.Message); } } public string ProcessImage( MemoryStream inputMemoryStream, string inputBlobName ) { FRDocument document = engineLoader.Engine.CreateFRDocument(); string resultBlobName = ""; try { document.PageFlushingPolicy = FREngine.PageFlushingPolicyEnum.PFP_KeepInMemory; // 문서에 이미지 파일 추가 displayMessage("Loading image..."); IntPtr handle = Marshal.AllocHGlobal(inputMemoryStream.GetBuffer().Length); Marshal.Copy(inputMemoryStream.GetBuffer(), 0, handle, inputMemoryStream.GetBuffer().Length); document.AddImageFileFromMemory(handle.ToInt64(), null, null); // 문서 인식 displayMessage("Recognizing..."); document.Process(null); // 결과 저장 displayMessage("Saving results..."); // 'balanced' 시나리오를 사용해 결과를 pdf로 저장 // FREngine.PDFExportParams pdfParams = engineLoader.Engine.CreatePDFExportParams(); // pdfParams.Scenario = FREngine.PDFExportScenarioEnum.PES_Balanced; WorkerRole1.Engine.FileWriter fileWriter = new WorkerRole1.Engine.FileWriter(inputBlobName, ".pdf"); resultBlobName = inputBlobName + ".pdf"; document.ExportToMemory(fileWriter, FREngine.FileExportFormatEnum.FEF_PDF, null); } catch (Exception error) { displayMessage("error: " + error.Message); } finally { // 문서 닫기 document.Close(); } return resultBlobName; } public void Dispose() { UnloadEngine(); } }}
Config.cs
class Config{ // ServiceDefinition.csdef에 정의된 로컬 스토리지 private static string LocalStorageName = "LocalStorage1"; // blob container에 연결하는 연결 문자열 public static readonly string ConnectionString = "<connection_string_to_frestorage>"; // 스토리지의 입력 및 출력 container 이름 public static readonly string InputContainerName = "fre-input"; public static readonly string OutputContainerName = "fre-output"; // ABBYY FineReader Engine의 Customer Project ID 반환 public static String GetCustomerProjectId() { return "<Your_Customer_Project_ID>"; } // 라이선스 토큰 이름 반환 public static String GetLicenseTokenName() { return "<Token_number>.ABBYY.ActivationToken"; } // 라이선스 password 반환 public static String GetLicensePassword() { return "<Your_Online_License_token_password>"; } // 엔진 경로 반환 public static String GetEngineFolder() { string engineSubfolder = "fre_packages\\LibraryPackage\\Bin64"; string engineDllFolder = Path.Combine(RoleEnvironment.GetLocalResource(LocalStorageName).RootPath, engineSubfolder); return engineDllFolder; }}