들어가며

RealGridJS에서 Excel로 export하는 방법에는 local방식과 remote방식이 있습니다.
local방식의 경우는 별다른 장치 없이 바로 파일로 저장하는 방법이지만,
remote방식의 경우는 서버로 보냈다가 다시 Stream형태로 다운로드받는 방법이라서 데이터를 받아서 Excel형태의 Stream으로 전달해주는 서버 기능이 필요합니다.

이 강좌에서는 이러한 서버 기능을 구현하는 방법에 대해 배워보겠습니다.

이론

RealGrid의 두가지 제품중 RealGridJS는 Excel 2007 포맷(xlsx)의 형태로 저장이 되고, RealGrid+의 경우는 type에 따라 “excel”인 경우는 Excel 97 포맷(xls)으로 “excelx”인 경우는 Excel 2007 포맷으로 저장이 됩니다. 이 강좌에서 다루는 서버 기능은 RealGridJS와 RealGrid+에서 type이 “excelx”일 경우 해당합니다. RealGrid+의 97 포맷의 엑셀은 RealGrid에서 전송하는 Request Body내용이 다음과 같이 다릅니다.

Excel2007 포맷

fileName=gridExportSample.xlsx&data=UEsDBAoAAAAIAPhJBEeAW5EPQAEAAB8EAAATAAAAW0NvbnRlbnRfVHlwZXNdLnhtbK2TyU7DMBRF90j8g%2BUtStyyQAg17YJhCZUoH2Dsl8aqJ%2Fm5pf17XtJBApUOoisrfvfec23Fg9HS...

Excel97 포맷

data=0M8R4KGxGuEAAAAAAAAAAAAAAAAAAAAAPgADAP7%2FCQAGAAAAAAAAAAAAAAABAAAAYwAAAAAAAAAA%0AEAAA%2Fv%2F%2F%2FwQAGAAAAAAAAAAAAAAABAAAAYwAAAAAAAAAA%0AEAAA%2Fv%2F%2F%2Fw...

즉, Request에 fileName parameter가 추가되었습니다. 이 강좌에서는 Excel 2007 형태의 서버모듈을 기준으로 진행됩니다.

또한 서버 기능을 다향한 프레임웍크에서 구현 할 수 있는데 여기서는 ASP.NET C# MVC, Java Spring MVC 두 개의 Framework를 기준으로 구현해 보겠습니다.

실습

  1. MVC Controler Binding Controler에 다음과 같이 Action을 등록합니다.

    ASP.NET C#

     [HttpPost]
     public ActionResult ExcelXBin()
     {
     }

    Java Spring

     @RequestMapping(value="ExcelXBin", method = RequestMethod.POST)
     public void excelXBin() throws Exception {
     }
  2. Request내용을 byte[]로 수신 문자열 형태의 Parameter로 전달받으면 Encoding된 문자열이 Unicode전환시 누락되는 경우가 있어서 byte[]로 받아서 ASCII 문자열로 전환하는 방식을 사용합니다.

    ASP.NET C#

     byte[] data = Request.BinaryRead(Request.TotalBytes);
     string formData = Encoding.ASCII.GetString(data, 0, data.Length);

    Java Spring

    java의 경우 HttpServletRequest 객체로부터 파일값을 읽어옵니다.

     public void excelXBin(HttpServletRequest request, HttpServletResponse response) throws Exception {
     }
  3. Request Content Parsing RealGridJS에서 전송되는 Request Body 값에는 fileName과 실제 data가 있습니다. 이를 분리하기 위해 다음의 과정이 필요합니다.

    ASP.NET C#

     string fileName = "realgridExcel.xlsx"; //기본 값
     string[] forms = formData.Split('&');
    
     foreach (string kvs in forms)
     {
         string[] kv = kvs.Split('=');
         if (kv[0].Equals("fileName") && !String.IsNullOrEmpty(kv[1])) // fileName 값이 없는 경우 기본 값 유지
         {
             fileName = kv[1];
         }
         else if (kv[0].Equals("data"))
         {
             formData = kv[1];
         }
     }

    Java Spring

     String fileName = request.getParameter("fileName");
     String data = request.getParameter("data");
  4. Decode 실제 엑셀데이터는 Base64 Encoding한 후 URL Encoding 되어서 전달되어집니다. 따라서 서버에서는 이를 반대로 URL Decode후 Base64 Decode 해야 원래 값으로 복원할 수 있습니다.

    ASP.NET C#

     formData = System.Web.HttpUtility.UrlDecode(formData);
     byte[] fileData = System.Convert.FromBase64String(formData);

    Java Spring

     byte[] filedata = Base64.decode(data);
  5. Response Header 서버에서 Stream을 내려보냈을 때 브라우저에서 엑셀 파일로 판단할 수 있도록 하기 위해서 Content-Type과 내려받을 파일명을 지정하기 위해 Content-Disposition을 Response의 Header로 지정해야 합니다. 또한 Response값이 Cache되지 않고 항상 서버에서 받아오도록 하기 위해서 no-cache도 지정합니다.

    ASP.NET C#

     Response.AddHeader("Content-Disposition", "attachment; filename=\"" + fileName + "\"");
     Response.AddHeader("Content-Type", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"); //Excel 2007의 Mime Type
     Response.AddHeader("Pragma", "no-cache");

    Java Spring

     response.addHeader("Content-Disposition", "attachment; filename=\"" + fileName + "\"");
     response.addHeader("Content-Type","application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"); //Excel 2007의 Mime Type
     response.addHeader("Pragma", "no-cache");
  6. 데이터를 Response에 저장 fileData 변수에 저장된 엑셀 데이터를 Response에 출력합니다.

    ASP.NET C#

     Response.BinaryWrite(fileData);
     Response.End();

    Java Spring

     OutputStream output = response.getOutputStream();
     output.write(fileData);
     output.flush();

전체 소스코드

ASP.NET C#
[HttpPost]
public ActionResult ExcelXBin()
{
    // Receive
    byte[] data = Request.BinaryRead(Request.TotalBytes);
    string formData = Encoding.ASCII.GetString(data, 0, data.Length);

    // Parsing
    string fileName = "realgridExcel.xlsx";
    string[] forms = formData.Split('&');

    foreach (string kvs in forms)
    {
        string[] kv = kvs.Split('=');
        if (kv[0].Equals("fileName") && !String.IsNullOrEmpty(kv[1]) ) 
        {
            fileName = kv[1];
        }
        else if (kv[0].Equals("data"))
        {
            formData = kv[1];
        }
    }

    // Decode
    formData = HttpUtility.UrlDecode(formData);
    byte[] fileData = Convert.FromBase64String(formData);

    // Response Init (없어도 돼지만 안전을 위해서 추가)
    Response.Clear();
    Response.ClearHeaders();
    Response.ClearContent();

    // Response Header
    Response.AddHeader("Content-Disposition", "attachment; filename=\"" + fileName + "\"");
    Response.AddHeader("Content-Type", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
    Response.AddHeader("Pragma", "no-cache");
    Response.Flush();

    // Write
    Response.BinaryWrite(data);
    Response.End();

    return View();
}
Java Spring
//HTTP POST

import com.sun.org.apache.xerces.internal.impl.dv.util.Base64;

@RequestMapping(value="ExcelXBin", method = RequestMethod.POST)
public void excelXBin(HttpServletRequest request, HttpServletResponse response) throws Exception {
	// Receive
	String fileName = request.getParameter("fileName");
	String data = request.getParameter("data");
	
	if (data.length() > 0) {
		// Decode
		byte[] filedata = Base64.decode(data);
		
		// Response Header
		response.addHeader("Content-Disposition", "attachment; filename=\"" + fileName + "\"");
		response.addHeader("Content-Type","application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
		response.addHeader("Pragma", "no-cache");
		
		// Write
		OutputStream os = response.getOutputStream();
		os.write(filedata);
		os.flush();
	}
}