2010年12月2日 星期四

JSP & Servlet 檔案上傳範例-使用Cos套件

Java EE規格雖然是針對網路應用開發的,但直到 Servlet 3.0 前都沒有提供檔案上傳的功能
在目前還沒打算轉換到Servlet 3.0 的打算下,先研究使用套件的解決方案
時下較為流行的上傳套件共有 Cos、FileUpload、SmartUpload
Cos套件是 O'reilly 公司提供,可至 http://www.servlets.com/cos 下載套件(cos-26Dec2008.zip)
FileUpload 的下載位置: http://commons.apache.org/fileupload/
FileUpload 使用須具備的套件:http://commons.apache.org/io/
SmartUpload 的網站已關閉,大概未來也不會繼續開發了

FileUpload 具有較多的功能,也有提供方便用來做 Ajax 應用的 listener
不過純以上傳效率來說Cos是最優秀的,贏過其他套件不少
這次實作選擇使用Cos

先提到上傳的介紹
撰寫上傳的功能必須先撰寫好一個頁面
並由表單以POST的形式將資料傳送到上傳處理的頁面
表單的編碼方式也與一般有所不同,enctype屬性共有三種值
1.application/x-www-form-urlencoded 是預設的編碼方式,它只處理表單域裡的value值
    採用這種編碼方式的表單會將表單域的值處理成URL編碼方式
2.multipart/form-data 編碼會以二進制流的方式來處理表單數據
   它把文件域指定文件的內容也封裝到請求參數里
   一旦設置了這種方式,就無法透過HttpServletRequest.getParameter()請求獲取請求參數
3.text/plain 編碼方式當表單的action屬性為mailto:URL的形式時比較方便
    這種方式主要適用於直接通過表單發送郵件的方式

這次實作的內容為撰寫一個提供圖片及附件上傳的功能
先寫好提供表單的網頁
designUploader.jsp======

.....
      <form action="UploadHandler" method="POST" enctype="multipart/form-data">
          <label>正面設計圖:</label><input type="file" id="f_pic" name="f_pic" value="" width="20" /><br />
          <label>背面設計圖:</label><input type="file" id="b_pic" name="b_pic" value="" width="20" /><br />
          <label>左側設計圖:</label><input type="file" id="l_pic" name="l_pic" value="" width="20" /><br />
          <label>右側設計圖:</label><input type="file" id="r_pic" name="r_pic" value="" width="20" /><br />
          <label>附件:</label><input type="file" id="appendix0" name="appendix0" value="" width="20" /><br />
          <input type="submit" value="Upload" /><br />
          <span>${uploadInfo.message}</span>
      </form>
....

解壓縮下載好的 Cos套件,並將 lib 裡的 cos.jar 複製到 WEB_INF 目錄底下的 lib 裡
接著撰寫處理上傳的頁面

使用Cos時,可以使用兩個類別來進行上傳工作:
1.  MultipartRequest     2.  MultipartParser

一般情況下使用MultipartRequest即可,不需要複雜的設定就可以輕鬆上傳物件
實際上MultipartRequest封裝了MultipartParser
在構造MultipartRequest實例時,建構了MultipartParser實例
建構 parser 的過程取得了上傳的 InputStream,但並不會真正讀取
然後透過 MultipartParser的readNextPart() method,從request流中讀取數據
區別出流中的參數域和文件域
如果是參數的話用ParamPart類封裝;如果是文件的話用FilePart封裝
此時如果設置了重命名策略的話,則在Server端新建一個新命名的空白物件
接著用FilePart的writeTo(saveDir)方法將流數據寫到硬碟中,文件上傳完成

MultipartRequest 範例:
UploadHandler.java=========

package tw.vencs;

import java.io.IOException;
import java.util.Enumeration;
import javax.servlet.http.*;
import com.oreilly.servlet.MultipartRequest;

public class UploadHandler extends HttpServlet{

      public void doPost(HttpServletRequest request, HttpServletResponse response)throws IOException{
           //uploaded files' store dictionary
           String saveDirectory = .....
           //limit of file capacity
           int maxPostSize=5*1024*1024;

           String FileName=null;         
           //declare file type
           String ContentType=null;
         
           //count numbers of file uploaded
           int count = 0;

           MultipartRequest multi = new MultipartRequest(request,saveDirectory,maxPostSize, "UTF-8");
           //取得所有上傳之檔案輸入型態名稱及敘述
           Enumeration filename=multi.getFileNames();
           Enumeration filesdc=multi.getParameterNames();
         
           while(filename.hasMoreElements()){
                  String name=(String)filename.nextElement();
                  String dc=(String)filesdc.nextElement();
                  FileName=multi.getFilesystemName(name);
                  ContentType=multi.getContentType(name);
                  if(FileName!=null){
                   count++;
                  }
           }
           ............
      }
}

當 MultipartRequest 建立好的時候就是檔案上傳完畢的時候
不設定檔案上傳大小時,最大上傳限制預設是 1mb
如果上傳檔案大小超過設定的大小時會丟出例外
雖然看API的說明是會丟出 ExceededSizeException,不過結果似乎是裝在IOException


命名策略是指上傳的檔案遇到了檔名重複時的處理方式
有自訂處理方式可以自行撰寫,如下:
RandomFileRenamePolicy.java===========

package tw.vencs;

import java.io.File;
import java.util.Date;
import com.oreilly.servlet.multipart.FileRenamePolicy;

public class RandomFileRenamePolicy implements FileRenamePolicy {

    public File rename(File file) {
      String body="";
      String ext="";
      Date date = new Date();
      int pot=file.getName().lastIndexOf(".");
      if(pot!=-1){
          body= date.getTime() +"";
          ext=file.getName().substring(pot);
      }else{
          body=(new Date()).getTime()+"";
          ext="";
      }
      String newName=body+ext;
      file=new File(file.getParent(),newName);
      return file;

    }
}

這個命名規則會取得時間來做為隨機命名的依據,接著將MultipartRequest改寫成:
RandomFileRenamePolicy rfrp=new RandomFileRenamePolicy();
MultipartRequest multi = new MultipartRequest(request,saveDirectory,maxPostSize, "UTF-8",rfrp);

因為我實作的需求是希望能限制上傳檔案的大小
不管是怎麼樣的上傳套件都沒辦法再接收資料前得知檔案大小
畢竟那等同於Server端直接入侵Client讀取資料,權限上不會被允許
JavaScript可以做到檢查檔案大小的功能
<script language="JavaScript"> 
   
    //這裡控制要檢查的項目,true表示要檢查,false表示不檢查 
    var isCheckImageType = true;  //是否檢查圖片副檔名 
    var isCheckImageWidth = true;  //是否檢查圖片寬度 
    var isCheckImageHeight = true;  //是否檢查圖片高度 
    var isCheckImageSize = true;  //是否檢查圖片檔案大小 
   
    var ImageSizeLimit = 100000;  //上傳上限,單位:byte 
    var ImageWidthLimit = 1200;  //圖片寬度上限 
    var ImageHeightLimit = 1000;  //圖片高度上限 
   
    function checkFile() { 
        var f = document.FileForm; 
        var re = /\.(jpg|gif)$/i;  //允許的圖片副檔名 
        if (isCheckImageType && !re.test(f.file1.value)) { 
            alert("只允許上傳JPG或GIF影像檔"); 
        } else { 
            var img = new Image(); 
            img.onload = checkImage; 
            img.src = f.file1.value; 
        } 
    } 
    function checkImage() { 
        if (isCheckImageWidth && this.width > ImageWidthLimit) { 
            showMessage('寬度','px',this.width,ImageWidthLimit); 
        } else if (isCheckImageHeight && this.height > ImageHeightLimit) { 
            showMessage('高度','px',this.height,ImageHeightLimit); 
        } else if (isCheckImageSize && this.fileSize > ImageSizeLimit) { 
            showMessage('檔案大小','kb',this.fileSize/1000,ImageSizeLimit/1000);         
        } else { 
            document.FileForm.submit(); 
        } 
    } 
    function showMessage(kind,unit,real,limit) { 
        var msg = "您所選擇的圖片kind為 real unit\n超過了上傳上限 limit unit\n不允許上傳!" 
        alert(msg.replace(/kind/,kind).replace(/unit/g,unit).replace(/real/,real).replace(/limit/,limit)); 
    } 
</script>


不過預防Client端沒開啟JavaScript的情形,我再查了些資料
java.io套件裡的 FileInputStream 有available() method可以檢查request送來的資料流大小
而不必等到資料流全傳輸完畢
可惜的是看過 FileInputStream 的API後知道available()的限制
它處理的是沒遭遇到network blocking時所送來的一串資料流,只是現實上常有blocking發生...
MultipartRequest最多是在全部的物件上傳完後再來檢查
既然傳輸資料的過程已經免不了了,那我希望至少是當一個檔案上傳時就先檢查
而省掉全傳輸完所耗費的資源及時間
所以將 UploadHandler 改寫成以 MultipartParser 處理
UploadHandler.java=========

package tw.vencs;

import java.io.File;
import java.io.IOException;
import javax.servlet.http.*;
import com.oreilly.servlet.multipart.FilePart;
import com.oreilly.servlet.multipart.MultipartParser;
import com.oreilly.servlet.multipart.Part;

public class UploadHandler extends HttpServlet{

      public void doPost(HttpServletRequest request, HttpServletResponse response)throws IOException{
          HttpSession session = request.getSession();
         
          //uploaded files' store dictionary
          String saveDirectory = null;
          //try &catch to prevent login session has been canceled
         
          File dir = new File(saveDirectory);
         
          int pictureSize = Integer.parseInt(getServletConfig().getInitParameter("pictureSize"));
          int appendixSize = Integer.parseInt(getServletConfig().getInitParameter("appendixSize"));
          int maxPostSize=pictureSize*4 + appendixSize;         //limit of file capacity
         
          String Message = "";
          
              MultipartParser fileMap = null;
              String fileName = null;              
            
              try{
                  fileMap = new MultipartParser(request, maxPostSize);
                  fileMap.setEncoding("UTF-8");

                  Part part = null;
             
                   while ((part = fileMap.readNextPart()) != null){
                       if(part.isFile()){         //if this inputstream is from HTML file upload element
                           FilePart filePart = (FilePart) part;
                           filePart.setRenamePolicy(new DefaultFileRenamePolicy());
                           String name = part.getName();        //get HTML elements' name of form
                           fileName = filePart.getFileName();   //get name of uploaded object
                         
                           if(fileName != null && !fileName.equals("")){                          
                             
                               String fileType = fileName.substring(
                                                   fileName.lastIndexOf('.')+1).toLowerCase();  //get file type
                               String tempMessage = null;

                               if(name.substring(2).equals("pic")){
                                   File dir = new File(saveDirectory);
                                   dir.mkdirs();
                                   //write data into designated file directory
                                   long size = filePart.writeTo(dir);

                                 
                                   if(fileType.equals("jpg") || fileType.equals("jpeg") ||
                                     fileType.equals("png") || fileType.equals("bmp") ||
                                     fileType.equals("gif") || fileType.equals("ai")){
                                     
                                      if(size > (long)pictureSize){
                                          tempMessage = fileName + "大小超過限制!!<br />";
                                          Message += (tempMessage);
                                          File disposedFile = new File(
                                                                             saveDirectory + "/" + filePart.getFileName());
                                          disposedFile.delete();
                                      }else{
                                          session.setAttribute(
                                                         name, saveDirectory + "/" + filePart.getFileName());
                                          tempMessage = fileName + "上傳成功!!<br />";
                                          Message += tempMessage;
                                      }
                                        
                                   }else{
                                       tempMessage = fileName + "不是圖檔格式!!<br />";
                                       Message += tempMessage;
                                       File disposedFile = new File(saveDirectory + "/" + fileName);
                                       disposedFile.delete();
                                   }
                               }else{                                        //appendix check
                                  File appendDir = new File(saveDirectory + "/appendix");
                                  appendDir.mkdirs();
                                  int size = (int)filePart.writeTo(appendDir);
                                  int uAppendSize = 0;                    //store uploaded data size info
                                
                                  if(session.getAttribute("append") != null &&
                                    !session.getAttribute("append").equals("")){
                                    
                                      File[] fList = appendDir.listFiles();
                                         for (int j = 0; j < fList.length; j++){
                                             FileInputStream in = new FileInputStream(fList[j]);
                                             uAppendSize += in.available();
                                             in.close();         //must close FileInputStream after use it
                                         }
                                  }
                                 
                                    if(size > (appendixSize - uAppendSize)){
                                        tempMessage = fileName + "大小超過限制!!<br />";
                                        Message += tempMessage;
                                        File disposedFile = new File(
                                               saveDirectory + "/appendix/" + filePart.getFileName());
                                        disposedFile.delete();  
                                    }else{
                                        String temp = "";
                                        if(session.getAttribute("append")!= null &&
                                           !session.getAttribute("append").equals("")){
                                            temp = session.getAttribute("append").toString();
                                        }
                                        temp += (saveDirectory +
                                            "/appendix/" + fileName + "=" + filePart.getFileName() + ",");
                                        session.setAttribute("append", temp);
                                      
                                        tempMessage = fileName + "上傳成功!!<br />";
                                        Message += tempMessage;
                                    }
                               }
                           }
                      }
                   }
              }catch(IOException e){
                  session.setAttribute("targetPage", "designUploader.jsp");
                  session.setAttribute("Message", "上傳檔案總大小超過限制!!<br />");
                  response.sendRedirect("designUploader.jsp");
                  return;
              }
              session.setAttribute("targetPage", "designUploader.jsp");
              session.setAttribute("Message", Message);
              response.sendRedirect("designUploader.jsp");
              return;
          }
      }
}

預設的命名規則是遇到覆蓋已存在的同名檔案
因為我想處理的只有上傳域的物件
假使表單裡還有其他文件域的資訊,如下處理:
 if (part.isParam()){              //if this inputstream is from normal HTML input element       
       ParamPart paramPart = (ParamPart) part;
       String value = paramPart.getStringValue();  //get param's value

2010年12月1日 星期三

[轉載] HttpClient 4.x 表單檔案上傳

最近手邊的專案漸漸由 HttpClient 3.x 轉移至改用 HttpClient 4.x 實作 Http 操作。
使用 Commons 專案的東西,捨棄原始的 HttpURLConnection 就是為了較輕鬆地處理 Http 操作的功能。

我們可以用下列方式透過網頁表單上傳檔案:

HttpClient client = new DefaultHttpClient();
HttpPost post =new HttpPost("http://example.com/upload.do");
MultipartEntity e = new MultipartEntity();
try{
        e.addPart("field1", new StringBody("some value"));
        e.addPart("file", new FileBody(new File("your_file_path")));
}catch (UnsupportedEncodingException ex) {
        ex.printStackTrace();
}

post.setEntity(e);
client.execute(post);

它的寫法是相當容易的!不過有個小缺點,那就是 HttpClient 4.x 某個版本將 Multipart Entity 實作獨立至 HttpMime 專案
http://hc.apache.org/httpcomponents-client/httpmime/index.html
所以,您要使用單純的表單上傳功能,就需要同時再引用 HttpMime 相關的 Library

C:\http-mime>dir
    磁碟區 C 中的磁碟沒有標籤。
    磁碟區序號: A8B9-DC3D

C:\http-mime 的目錄

2010/01/01 下午 01:20 <DIR> .
2010/01/01 下午 01:20 <DIR> ..
2009/12/31 下午 02:22 345,048 apache-mime4j-0.6.jar
2009/12/31 下午 02:22 25,993 httpmime-4.0.jar
2009/12/31 下午 02:22 2,255 jcip-annotations-1.0.jar
                    3 個檔案 373,296 位元組
                    2 個目錄 70,042,320,896 位元組可用

雖然,這些 Library 僅 300 多 kb,對一般的應用程式開發來說並不算什麼。
但總覺得是否能讓它單純一些呢?我們並不需要完整而強大的 Http Mime,
有時我們只需要足夠的功能罷了!隨著這個想法,試著實作一個極簡版本的 ThinMultipartEntity。
可以不需引用 HttpMime 就上傳檔案。

package com.google.jplurk.net;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Random;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.http.Header;
import org.apache.http.HttpEntity;
import org.apache.http.message.BasicHeader;

public class ThinMultipartEntity implements HttpEntity {

  static Log logger = LogFactory.getLog(ThinMultipartEntity.class);

  private final static char[] MULTIPART_CHARS =
"-_1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
      .toCharArray();

  private String boundary = null;

  ByteArrayOutputStream out = new ByteArrayOutputStream();
  boolean isSetLast = false;
  boolean isSetFirst = false;

  public ThinMultipartEntity() {
    StringBuffer buf = new StringBuffer();
    Random rand = new Random();
    for (int i = 0; i < 30; i++) {
      buf.append(MULTIPART_CHARS[rand.nextInt(MULTIPART_CHARS.length)]);
    }
    this.boundary = buf.toString();

  }

  public void writeFirstBoundary(){
    if(!isSetFirst){
      try {
        out.write(("--" + boundary + "\r\n").getBytes());
      } catch (IOException e) {
        logger.error(e.getMessage(), e);
      }
    }
    isSetFirst = true;
  }

  public void writeLastBoundary() {
    if(isSetLast){
      return ;
    }
    try {
      out.write(("\r\n--" + boundary + "--\r\n").getBytes());
    } catch (IOException e) {
      logger.error(e.getMessage(), e);
    }
    isSetLast = true;
  }

  public void addPart(String key, String value) {
    writeFirstBoundary();
    try {
      out.write(("Content-Disposition: form-data; name=\"" +key+"\"\r\n").getBytes());
      out.write("Content-Type: text/plain; charset=UTF-8\r\n".getBytes());
      out.write("Content-Transfer-Encoding: 8bit\r\n\r\n".getBytes());
      out.write(value.getBytes());
      out.write(("\r\n--" + boundary + "\r\n").getBytes());
    } catch (IOException e) {
      logger.error(e.getMessage(), e);
    }
  }

  public void addPart(String key, File value) {
    writeFirstBoundary();
    try {
      out.write(("Content-Disposition: form-data; name=\""
+ key+"\"; filename=\"" + value.getName()+ "\"\r\n").getBytes());
      out.write("Content-Type: application/octet-stream\r\n".getBytes());
      out.write("Content-Transfer-Encoding: binary\r\n\r\n".getBytes());

      FileInputStream fin = new FileInputStream(value);
      int data = fin.read();
      while(data !=-1){
        out.write(data);
        data = fin.read();
      }

    } catch (IOException e) {
      logger.error(e.getMessage(), e);
    }
  }

  public long getContentLength() {
    writeLastBoundary();
    return out.toByteArray().length;
  }

  public Header getContentType() {
    return new BasicHeader("Content-Type", "multipart/form-data; boundary=" + boundary);
  }

  public boolean isChunked() {
    return false;
  }

  public boolean isRepeatable() {
    return false;
  }

  public boolean isStreaming() {
    return false;
  }

  public void writeTo(OutputStream outstream) throws IOException {
    outstream.write(out.toByteArray());
  }

  public Header getContentEncoding() {
    return null;
  }

  public void consumeContent() throws IOException,
      UnsupportedOperationException {
    if (isStreaming()) {
      throw new UnsupportedOperationException(
          "Streaming entity does not implement #consumeContent()");
    }
  }

  public InputStream getContent() throws IOException,
      UnsupportedOperationException {
    throw new UnsupportedOperationException(
        "Multipart form entity does not implement #getContent()");
  }

}

PS. 因為是極簡版的,有許多細節是被省略的
PS. 只試過上傳一個檔 XD 

2010年11月27日 星期六

[轉載] Apache HttpClient 4.x 使用 GET, POST 範例

先前我曾經發了一篇介紹如何抓取網頁的教學:
利用Jakarta.Commons.HttpClient抓取網頁、網站(Parser),不過當時用的是 HttpClient 3.1。
經過時間的演進 Apache 已經在 14 August 2009 發佈HttpComponents HttpClient 4.0 (GA)
由 3.1 到 4.0 因為底層幾乎全部重新改寫,所以也使有些舊的程式無法使用。
這篇就是我自己寫的一個簡單範例。

在看範例之前先把一些重要連結整理給大家:
想知道這次到底更動了哪些東西可以看:Apache HttpClient 首頁
官方的 Tutorial 在:Apache HttpClient Tutorial
而 API DOC、說明文件則在:Apache HttpClient apidocs

相關的程式碼、jar 檔在:HttpComponents HttpClient 4.0 (GA)
注意,在寫程式前必需先將四個 jar 檔正確匯入,最後兩個(*)是選用,
請參考:http://hc.apache.org/httpcomponents-client-ga/quickstart.html

  • commons-logging-x.x.x.jar
  • commons-codec-x.x.x.jar
  • httpcore-x.x.x.jar
  • httpclient-x.x.x.jar
  • apache-mime4j-x.x.x.jar (*)
  • httpmime-x.x.x.jar (*)
說了這麼多,以下是程式的範例,
第一個是傳回在 google 查詢 httpclient 的結果。
第二則是傳回台大圖書館查詢 Head First Java 的結果。

 package demo.httpclient;

import java.io.IOException;
import java.util.ArrayList;

import org.apache.commons.httpclient.HttpStatus;
import org.apache.http.HttpResponse;
import org.apache.http.NameValuePair;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.utils.URLEncodedUtils;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.util.EntityUtils;

/**
* Apache HttpClient 4.x 使用 GET, POST 查詢網頁的範例
*
* @author werdna at http://werdna1222coldcodes.blogspot.com/
*/

public class HttpClientDemo extends DefaultHttpClient {

public static void main(String[] args) throws IOException {

DefaultHttpClient demo = new DefaultHttpClient();
demo.getParams().setParameter("http.protocol.content-charset", "UTF-8");

// Get Request Example,取得 google 查詢 httpclient 的結果
HttpGet httpGet = new HttpGet("http://www.google.com.tw/search?q=httpclinet");
HttpResponse response = demo.execute(httpGet);
String responseString = EntityUtils.toString(response.getEntity());
if (response.getStatusLine().getStatusCode() == HttpStatus.SC_OK) {
// 如果回傳是 200 OK 的話才輸出
System.out.println(responseString);
} else {
System.out.println(response.getStatusLine());
}

// Post Request Example,查詢台大圖書館書籍
ArrayList<NameValuePair> pairList = new ArrayList<NameValuePair>();
pairList.add(new BasicNameValuePair("searchtype", "t"));
pairList.add(new BasicNameValuePair("searchscope", "keyword"));
pairList.add(new BasicNameValuePair("searcharg", "Head First Java"));
pairList.add(new BasicNameValuePair("SORT", "D"));

HttpPost httpPost = new HttpPost("http://tulips.ntu.edu.tw:1081/search*cht/a?");
StringEntity entity = new StringEntity(URLEncodedUtils.format(pairList, "UTF-8"));
httpPost.setEntity(entity);
response = demo.execute(httpPost);
responseString = EntityUtils.toString(response.getEntity());
if (response.getStatusLine().getStatusCode() == HttpStatus.SC_OK) {
// 如果回傳是 200 OK 的話才輸出
System.out.println(responseString);
} else {
System.out.println(response.getStatusLine());
}
}
}
更多的程式範例可以參考其教學: 
HttpClient 4 HTTP request 
HttpClient 4 使用POST方式提交普通表單數據的例子

關鍵字:Apache、HttpClient、4、教學、示範、範例、設定
參考資料:
  1. Apache HttpClient 首頁
  2. HttpComponents HttpClient 4.0 (GA)
  3. Apache HttpClient Tutorial
  4. Apache HttpClient apidocs
  5. HttpClient 4 HTTP request
  6. HttpClient 4 使用POST方式提交普通表單數據的例子 

2010年11月16日 星期二

[轉載] JSON傳送與接收


原文

XML可用來表現階層性資料,然而建立與處理XML DOM較為複雜
在Web應用程式中,許多時候並不需要用到XML的複雜階層性
此時通常會採用JSON作為資料交換格式
而JSON也正成為Web應用程式中交換資料的主要選擇。

JSON全名JavaScript Object Notation,為彷造JavaScript的物件實字(Object literal)格式而來
你可以在 http://www.json.org/ 找到詳細的JSON格式說明
大致而言,與物件實字格式類似,主要注意的是JSON:
作為名稱的部份,必須用"雙引號包括
作為值的部份,若為字串,必須用"雙引號包括
不支援函式表示

舉個例子來說,下面是個物件實字:
var obj = {
    name : 'Justin',
    age : 35,
    childs : [ { name : 'hamimi', age : 3} , { name : 'another', age : 5}]
};

若使用JSON表示,則是如下:
var json = '{"name":"Justin","age":35,"childs":[{"name":"hamimi","age":3}, { "name" : "another", "age" : 5}]}';

若為排版會比較容易觀察:
{
    "name":"Justin",
    "age":35,
    "childs":[
        {
            "name" : "hamimi",
            "age" : 3
        },

       {
            "name" : "another",
            "age" : 5
        }

    ]
}

你可以傳送JSON字串給伺服端,要建立JSON字串很簡單
在Firefox 3.1、Internet Explorer 8以上,支援簡單的JSON處理程式庫
如果要從JavaScript建立JSON字串,只要使用JSON.stringify(),例如:
var obj = {
    name : 'Justin',
    age : 35,
    childs : [ { name : 'hamimi', age : 3}, { "name" : "another", "age" : 5} ]
};
var json = JSON.stringify(obj);

這樣就可以得到方才所示範的JSON字串,如果要用非同步物件傳送JSON
則可以如下:
var request = xhr();     // xhr() 會建立非同步物件
request.onreadystatechange = handleStateChange;  // handleStateChange 參考至函式
request.open('POST', url);
request.setRequestHeader('Content-Type', 'application/json');
request.send(json);

請求標頭'Content-Typ'建議設為'application/json'
當然,伺服端要能夠剖析JSON字串以取出資料,但無需親自撰寫
http://www.json.org/ 網站中提供許多語言實作的JSON剖析器協助剖析JSON取得結果

伺服端可以傳回JSON字串
可以使用eval()將JSON字串計值(evaluate)為JavaScript物件
例如:
var obj = eval(json);

然而eval()也會運算傳入的JavaScript程式碼,因此並不建議直接用eval()
如果只是要計值JSON為JavaScript物件,可以使用JSON.parse()。例如:
var obj = JSON.parse(json);

如果是Firefox 3.1、Internet Explorer 8以外沒有內建JSON支援的瀏覽器
可以在 http://www.json.org/ 下載 json2.js
將之包括在網頁中,即可獲得上述的相關JSON方法:
<script type="text/javascript" src="json2.js">
如果遇到瀏覽器已內建JSON支援,json2.js什麼都不作

2010年11月12日 星期五

[轉載] javascript :onsubmit=return false阻止form表單提交

return false 阻止表單提交不起今天這個問題困擾了我很久,在網上找了很多資料,基本上關於onsubmit=return false有以下幾點要注意的地方:

1. return 的返回值問題,函數中return一旦有返回值,就不在執行下面的語句,直接跳到函數調用 的地方。如下PHP函數代碼,第一個if條件符合則函數值返回布爾型false,可以返回一個函數的值,並且跳出這個函數;只要遇到return語句,程序就在那一行代碼停止執行,執行控制將立刻返回到調用該程序的代碼處。
 function chkinput(form)
 {
   if(form.title.value=="")
    {
      alert("請輸入文章標題!");
   form.title.select();
   return false;              //注意不能寫成 return(false);
    }

   if(form.content.value=="")
    {
      alert("文章正文不能為空@!!");
   form.content.select();
   return false;
    }

    return true;
 }

2.form的onsubmit屬性的觸發問題,onsubmit 事件什麼時候觸發?onsubmit 事件會在表單中的確認按鈕被點擊時發生。不觸發的原因有一般如下:
A. onsubmit屬性的觸發時機是在form用input:submit這樣的button提交時才會觸發,否則不會觸發。如果是用一個普通input:button,則在onclick屬性中指定一個javascript函數,在這個函數裡面再執行form的submit()函數,而不是onsubmit屬性。
B. 先看一段代碼:
<form action="index.jsp" method="post" onsubmit="submitTest();">

<INPUT value="www">

<input type="submit" value="submit">

</form>
<SCRIPT LANGUAGE="JavaScript">

<!--

function submitTest() {

// 一些邏輯判斷return false;

}

//--></SCRIPT>
點擊submit按鈕該表單並未提交。因為有一處應該改為(紅色字體)
<form action="index.jsp" method="post" onsubmit="return submitTest();">
原來onsubmit屬性就像是<form>這個html對象的一個方法名,其值(一字符串)就是其方法體,默認返回true;
和Java一樣,在該方法體中你可以寫任意多個語句,包括內置函數和自定義函數。
在這裡submitTest()雖然返回false,但我們只執行了此函數,沒有對其結果進行任何處理。
onsubmit="return submitTest()利用到了它的返回值,達到了預期效果。
 
3.事件處理函數返回false的問題,在大多數情況下,為事件處理函數返回false,可以防止默認的事件行為.
例如,默認情況下點擊一個<a>元素,頁面會跳轉到該元素href屬性指定的頁.   
Return False 就相當於終止符,Return True 就相當於執行符。  
在js中return false的作用一般是用來取消默認動作的。比如你單擊一個鏈接除了觸發你的  
onclick時間(如果你指定的話)以外還要觸發一個默認事件就是執行頁面的跳轉。所以如果  
你想取消對象的默認動作就可以return false。return false應用比較多的場合有:  

<body>  
1, <a href="a.JSP" mce_href="a.JSP" onclick=test();>超級鏈接 </a>  
2, <input type="button" onclick=test() value="提交">  
3, <form name="form1" onsubmIT="return test();">  
內容  
<input type="submIT" value="提交">  
</form>  
</body>  

    
<input type="submit" onclick="submitAction(); return false;" />  
submitAction 方法裡面有提交表單的動作。如果不加 return false,
在執行完 submitAction 之後,submit 按鈕還會繼續執行它的默認事件,
就會再次提交表單。這可能就是很多錯誤的根源。   
的確,return false的含義不是阻止事件繼續向頂層元素傳播,
而是阻止瀏覽器對事件的默認處理。你可以這樣試驗:
首先將所有的js腳本註釋掉,在IE瀏覽器中嘗試拖動一下圖片,
你會發現鼠標會成為禁止操作的樣式,圖片是被禁止拖動的,
它是瀏覽器針對mousemove事件所提供的默認行為。
return false就是為了去掉這種行為,否則就會出現你描述的中斷事件連續執行。   
另外,和return false等效的語句為:
window.event.returnValue = false,
你可以把return false替換為此語句並進行驗證。   
最後說明一下,此種方式只適用於IE瀏覽器。
在js中return false的作用一般是用來取消默認動作的。比如你單擊一個鏈接除了觸發你的 
onclick時間(如果你指定的話)以外還要觸發一個默認事件就是執行頁面的跳轉。所以如果
你想取消對象的默認動作就可以return false。return false應用比較多的場合有:
<form name="form1"  onsubmit="return youfunction();">...... </form>
<a href="www.***.com" onclick="...;return false;">dddd </a>

2010年11月7日 星期日

自訂編碼標籤範例

如果不將Eclipse的環境編碼改成UTF-8,似乎轉碼會有問題
因應這種情況寫了自訂標籤來協助處理

encrypt.tld=========

<?xml version="1.0" encoding="UTF-8" ?>
<taglib xmlns="http://java.sun.com/xml/ns/j2ee"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-jsptaglibrary_2_0.xsd"
    version="2.0">

    <tlib-version>1.2</tlib-version>            
    <short-name>encryptlib</short-name>    
    <uri>encrypt-tags</uri>                              
    <tag>
      <description>URL  get/post encrypting</description>
      <name>encrypt</name>
      <tag-class>tw.vencs.EncrptTag</tag-class>
      <body-content>empty</body-content>
    
      <attribute>
          <name>encryptValue</name>
          <required>true</required>
          <rtexprvalue>true</rtexprvalue>
      </attribute>
    </tag>

</taglib>

EncrptTag.java===============

package tw.vencs;

import javax.servlet.jsp.tagext.SimpleTagSupport;
import javax.servlet.jsp.JspException;
import java.io.IOException;
import java.util.logging.Level;
import java.util.logging.Logger;

public class EncrptTag extends SimpleTagSupport{
    private String encryptValue; 
   
    public void doTag() throws JspException { 
        try { 
            encryptValue = new String(encryptValue.getBytes("iso-8859-1"),"UTF-8"); 
            getJspContext().getOut().write(encryptValue);
        } catch (IOException e) { 
              Logger.getLogger(EncrptTag.class.getName()).log(Level.SEVERE, null, e);
              throw new RuntimeException();
        }    
    } 
    public void setEncryptValue(String encryptValue) { 
        this.encryptValue = encryptValue; 
    } 
}

Demo.jsp===============

.....
<%@ taglib prefix="e" uri="encrypt-tags" %>
.....
<body>
.....
....=<e:encrypt  encryptValue="${param.itemName}"/>
</body>
.....

2010年11月5日 星期五

[轉錄]Tomcat Post / Get 中文編碼處理方法 (中文亂碼問題)

最近在把原本佈署在 Resin 的 Java Web Application移植到 Tomcat 時,有許多原本正常的中文get / post 功能,都變的異常,只要是透過 get / post 得到的資料,就會變亂碼
剛好也驗證了 Resin 在Character Encoding的部分做的較好
而看看網路上大家的問題,也可以知道 Tomcat 在編碼處理的部分 真是讓很多人頭痛

主要的原因是,TOMCAT 預設都是用 ISO-8859-1 的編碼方式來傳遞資訊
這個問題,可以解決的方式整理如下:

1. JSP 頁面的編碼宣告需與實際儲存檔案時用的編碼一致

這是個很好玩的問題~ 有些人 在頁面宣告用 big5 or UTF-8 or... 字集
但是,檔案實際儲存的方式與該編碼不同,則 頁面當然會出現亂碼的問題

2. 自行轉碼
form method=POST or GET 時 因為Tomcat預設編碼是ISO-8859-1的關係
直接用 <%=request.getParameter("firstname")%> 取值,中文字會變亂碼
(你傳的明明是UTF-8, 但是讀取時 確用 ISO-8859-1來解譯.. 當然有問題囉)
要使用 <%=new String(request.getParameter("firstname").getBytes( "ISO-8859-1"), "UTF-8")%>這種方式來轉換編碼才會正常 (假設,頁面用的編碼為UTF-8時)

3. 透過設定 server.xml URIEncoding的方式 來處理 GET 亂碼問題
在 tomcat 的設定檔內 ./conf/server.xml 修改下列設定的話,則透過GET的方式傳遞參數
的話,Tomcat會用你指定的編碼方式來對待用get方法傳遞的參數,而不是使用預設的ISO-8859-1 (get 的方式是透過 url parameter傳遞參數)
但是,因為只處理 URIEncoding, 所以若使用 Post 傳遞,還是會有問題
URIEncoding: This specifies the character encoding used to decode the URI bytes, after %xx decoding the URL. If not specified, ISO-8859-1 will be used.

P.S. 若使用 GET Method, 且設定了URIEncoding 的話就不用再做上述二的自行轉碼動作,如果多加該處理,一樣會有亂碼的問題

P.S.2  如果整合apache和Tomcar的話,需要將兩者溝通用的8009 port也設置URIEncoding

4. 使用 request.setCharacterEncoding 來處理 GET / POST 的亂碼問題
在處理的頁面,加上下列的處理即可,明確的告訴Tomcat request的編碼為何,不要一廂情願的使用ISO-8859-1來解讀~

<%request.setCharacterEncoding("UTF-8");%>

javax.servlet.servletRequest.setCharacterEncoding(string env)
Overrides the name of the character encoding used in the body of this request. This method must be called prior to reading request parameters or reading input using getReader(). Otherwise, it has no effect.

5. 使用 filter 來處理 GET / POST 的亂碼問題
這個方法是最方便的,只要在 web.xml 裡面 透過 filter 的設定,讓每個網頁都能
透過 filter 的處理一體適用所有編碼問題的解決
方法如下:

a. 目前 Tomcat 提供的範例裡面,就有現成的 Encoding Filter, 位置如下:
.\Tomcat 6.0\webapps\examples\WEB-INF\classes\filters\SetCharacterEncodingFilter.class

b. 所以,只要把該 class 檔,copy 到你的 web application 的
.\WEB-INF\classes\filter 下,再搭配 web.xml 的filter 設定,
就可以讓所有符合 url pattern 的網頁,都能自動apply所想要的編碼設定(透過 encoding這個 parameter來設定),是不是很方便呢 ?
(這個功能 就像是自動把上述第四點的 setCharacterEncoding 語句 自動apply到各頁面囉)

------------
以上~ 就從 Resin Porting 到 Tomcat 的經驗,當然是使用 filter 的方式 一次解決~ 不然的話 就得每個網頁每個網頁去改囉~

延伸資料:
FAQ/CharacterEncoding
http://jim.blogsome.com/2005/05/27/jsp-chinese-character-solution/
http://www.javaworld.com.tw/confluence/pages/viewpage.action?pageId=752