顯示具有 Tomcat 標籤的文章。 顯示所有文章
顯示具有 Tomcat 標籤的文章。 顯示所有文章

2022年11月23日 星期三

配合 Fetch 使用 MediaSource 播放 FMP4 - 影片輸出使用 jsp 或 servlet

 html 裡用 <video> 播放 MP4 影片檔時,
需要完全的讀取完影片檔才能播放,如果影片本身較長檔案大小較大時,
就會需要等待較長的時間等影片讀取完後才能開始播放,
因為 <video> 需要讀完影片檔中含有影片資訊的 moov (Movie Box) 區塊才會開始播放影片。

我們可以使用 Bento4 工具來查看影片中的檔案結構,
使用 Bento4 的 mp4dump 命令執行以下指令:

D:\Bento4\bin\mp4dump xxx_video.mp4

就可以在命令列視窗中看到影片的結構,

通常的 MP4 除了一開始的含有影片版本協議等資訊的 ftyp (File Type Box) 區塊,
接著的會是一個含有影片資訊的 moov (Movie Box) 區塊和一個含有實際影片數據的 mdat (Media Data Box) 區塊 (其他區塊可先忽略),像這樣:

ftyp + mdat + moov

mdat 和 moov 的擺放位置順序可以換,如果 moov 在 mdat 之後的話,
<video> 會需要在讀取完 moov 後才會開始播放影片。

如果想要提前 <video> 的影片開始播放時間的話,有幾種解決方法:

  1. 第一個方法是把 moov 放到 mdat 之前,
    讓 <video> 先讀取到 moov 的影片資訊,
    這樣 <video> 就可以不用等到影片完全載入完就開始播放影片,
    檔案結構會像這樣:

    ftype + moov + mdat

    使用 ffmpeg 工具執行以下指令就可以將影片的 moov 搬到 mdat 前面,可參考"ffmpeg 常用命令"這篇文章:
    D:\ffmpeg\bin\ffmpeg -i "normal_video.mp4" -movflags faststart -acodec copy -vcodec copy "moov_moved_video.mp4"
    第二個方法是把 MP4 轉成 FMP4 (fragmented mp4),
  2. FMP4 跟一般的 MP4 不同,
    除了有 moov 以外,還有 moof (movie fragment) 。
    一般的 MP4 檔案結構為一個 moov 和一個 mdat,
    而 FMP4 的檔案結構把影片分割成多個片段 (fragment),
    每個片段由一組 moof 和一個 mdat 組成,
    moof 包含了後面跟著的 mdat 的資訊,
    所以 <video> 可以在讀取影片時,先對讀到的片段進行播放,再一邊繼續讀取剩餘的片段,
    檔案結構會像這樣:

    ftype + moof + mdat + moof + mdat + moof + mdat + ........

    使用 Bento4 工具的 mp4fragment 命令可以方便地將 MP4 影片轉成 FMP4,
    執行以下指令即可:
    D:\Bento4\bin\mp4fragment "normal_video.mp4" "fragmented_video.mp4"
    也可使用 ffmpeg 工具來進行 FMP4 的轉檔,以下是兩種我在網路上找到的指令,
    參數有點不同,不過親自實測都是可以成功轉檔的,
    但我使用轉出來的檔案實作邊下載邊播放時有點問題,
    有時會出現
    Uncaught (in promise) DOMException: Failed to execute 'appendBuffer' on 'SourceBuffer': The HTMLMediaElement.error attribute is not null.
    的錯誤訊息,所以我還是會先以用 Bento4 轉檔的檔案來做實作:
    D:\ffmpeg\bin\ffmpeg -i "normal_video.mp4" -strict -2 -movflags frag_keyframe+empty_moov "fragmented_video.mp4"

    D:\ffmpeg\bin\ffmpeg -i "normal_video.mp4" -movflags empty_moov+default_base_moof+frag_keyframe "fragmented_video.mp4"

成功把影片轉成 FMP4 後,就可以配合 Javascript 的 Fetch 功能實作邊下載影片檔邊播放的功能,在這裡我要展示一下使用 Javscript 的 Fetch 來邊讀取邊播放來自 servlet (或 jsp 或 nodeJs 等任何你喜歡的 server 程式碼)  的FMP4影片二進位流 (不支援非 FMP4 的影片):

首先是專案檔案結構,其中紅框部份是主要需要的檔案,此專案是一個 Java EE Web Application 專案,使用 Tomcat server 運行:




專案中放了三個測試的影片檔如下:

  1. normal_video.mp4:
    普通的影片檔,格式如:ftyp + mdat + moov
  2. moov_moved_video.mp4:
    把 moov 放到 mdat 之前的影片檔,格式如:ftype + moov + mdat
  3. fragmented_video.mp4:
    轉檔成 FMP4 的影片檔,格式如:ftype + moof + mdat + moof + mdat + moof + mdat + ........

首先是把影片檔用二進位輸出的 servlet, GetVideoBinary.java
/testWeb/src/test/GetVideoBinary.java:
package test;

import java.io.BufferedInputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.net.URLConnection;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

@WebServlet("/GetVideoBinary")
public class GetVideoBinary extends HttpServlet {
	private static final long serialVersionUID = 1L;
       
    public GetVideoBinary() {
        super();
    }

	protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		String videoType = request.getParameter("videoType");
		if (videoType == null) {
			videoType = "";
		}
		
		String videoSourceDir = "/source";
		String videoSourceName = "normal_video.mp4";
		switch(videoType) {
			case "moov_moved_video":
				videoSourceName = "moov_moved_video.mp4";
				break;
			case "fragmented_video":
				videoSourceName = "fragmented_video.mp4";
				break;
		}
		String videoSourcePath = videoSourceDir + "/" + videoSourceName;
		
		String contentType = URLConnection.guessContentTypeFromName(videoSourceName);
		if (contentType == null) {
			contentType = "application/octet-stream";
		}
		response.setContentType(contentType);
		
		try (BufferedInputStream inputStream = new BufferedInputStream(this.getClass().getResourceAsStream(videoSourcePath));
			 OutputStream outputStream = response.getOutputStream();
			){
			byte[] buffer = new byte[1024];
			int numRead = 0;
			while ((numRead = inputStream.read(buffer)) != -1) {
				outputStream.write(buffer, 0, numRead);
			}
		}
	}

	protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		service(request, response);
	}

	protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		doGet(request, response);
	}

}

在 GetVideoBinary.java 中,接受了一個 "videoType" 參數,來決定要讀取哪一個影片檔做輸出。

再來是主要的測試頁, index.html
/testWeb/WebContent/readableStreamTest/index.html:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Readable Stream Test</title>

<script src="js/fmp4Player.js"></script>
<script>
	readAndPlayFMP4("fragmented_video");
</script>

</head>

<body>
	<div>
		normal_video: 
		<video muted controls autoplay playsInline style="width: 500px;" id="normal_video" src="/GetVideoBinary?videoType=normal_video"></video>
	</div>
	<div>
		moov_moved_video: 
		<video muted controls autoplay playsInline style="width: 500px;" id="moov_moved_video" src="/GetVideoBinary?videoType=moov_moved_video"></video>
	</div>
	<div>
		fragmented_video:
		<video muted controls autoplay playsInline style="width: 500px;" id="fragmented_video"></video>
	</div>
</body>
</html> 

index.html 內容非常簡單,頁面中有三個 <video>, 分別用來播放上述的三個 MP4 影片,
normal_video.mp4 和 moov_moved_video.mp4 是直接設定影片 url 在 <video> 的 src 屬性上,
而 fragmented_video 則是呼叫了 "js/fmp4Player.js" 中的 readAndPlayFMP4 函式來載入影片。

最後我們來看一下 "js/fmp4Player.js",
/readableStreamTest/js/fmp4Player.js:

function readAndPlayFMP4(videoPlayerId){
	//使用 fetch 讀取影片二進位流
	fetch('/GetVideoBinary',
		{
			method : 'POST',
			headers : {
				'Content-Type': 'application/x-www-form-urlencoded'
			},
			body: "videoType=" + videoPlayerId
		}).then(function(response) {
			var videoPlayer = document.getElementById(videoPlayerId);
	
			var mimeCodec = 'video/mp4; codecs="avc1.42E01E, mp4a.40.2"';
	
			if ('MediaSource' in window && MediaSource.isTypeSupported(mimeCodec)) {
				var mediaSource = new MediaSource();
				videoPlayer.src = URL.createObjectURL(mediaSource);
				mediaSource.addEventListener('sourceopen', sourceOpen);
			} else {
				console.error('Unsupported MIME type or codec: ', mimeCodec);
			}
	
			function sourceOpen(_) {
				//console.log(this.readyState); // open
				var mediaSource = this;
				var sourceBuffer = mediaSource.addSourceBuffer(mimeCodec);
				
				var dataSetReductionStatus = []; // 用來顯示目前 dataset 被拆分的狀態以利觀察 Record current data reduction status for debug
				
				var reader = response.body.getReader();
				readNextDataset();
				
				function readNextDataset(){
					// 遞迴地讀取影片的二進位流資料 Read video data recursively
					return reader.read().then(function(response){
						if (response.done) {
							//讀取完畢,已沒有可讀取的影片二進位流資料,告訴 mediaSource 所有影片資料已讀取完畢
							mediaSource.endOfStream(); //tell media source that it reaches the end of stream, all data from steam has been send.
							console.log("All video data handling complete");
							return Promise.resolve();
						}else{
							// response.value is Uint8Array trype
							dataSetReductionStatus.unshift(response.value.byteLength);
							//把這一波讀到的影片資料 (response.value) 交由 handleData() 處理
							return handleData(response.value).then(function(){
								// 這波讀取的影片資料均已處理完畢,開始讀取下一波影片資料
								console.log("A set of data (total: " + response.value.byteLength + ") handling complete");
								readNextDataset();
							});
						}
					});
				}
				
				function handleData(data){
					return new Promise(function(resolve, reject){
						// 在 sourceBuffer.appendBuffer() 之後,必須等到 updateend 事件發生後才能再進行下一次的 sourceBuffer.appendBuffer()
						// Need to wait for "updateend" event to do next sourceBuffer.appendBuffer()
						sourceBuffer.addEventListener('updateend', sourceBufferUpdateendListener);
						function sourceBufferUpdateendListener(_) {
							console.log("updateend");
							dataSetReductionStatus.shift();
							sourceBuffer.removeEventListener('updateend', sourceBufferUpdateendListener);
							resolve();
						}
						
						try {
							// 印出 dataSetReductionStatus 觀察影片資料拆分的狀態
							console.log("dataSetReductionStatus: <-- " + dataSetReductionStatus + " <--");
							console.log("Try to append: " + data.byteLength);						
							sourceBuffer.appendBuffer(data); // sourceBuffer.appendBuffer() 有可能得到 "QuotaExceededError" 例外 May catch 'QuotaExceededError' exception
							console.log(data.byteLength + " data was appended.");
					    } catch (e) {
					    	sourceBuffer.removeEventListener('updateend', sourceBufferUpdateendListener);
					        if (e.name !== 'QuotaExceededError') {
					        	throw e;
				        	}
					        console.log("QuotaExceededError!");
				        	// 嘗試進行拆分影片資料分別處理,例如此例把影片拆分成總大小(byte) 80% 和 20% 大小的兩個部份 Split data and handle each reduced part of data, for example: reduction: 80%.
							var reduction = data.byteLength * 0.8;
							
							if (data.slice(0, reduction).byteLength == 0){
								// 如果拆分後 80% 那部份大小為 0 的話,就不拆分,還是處理 100% 的影片資料,但是等一段時間例如 1 秒後再處理
								// 以遞迴方式處理資料
								console.log("Try to append " + data.byteLength + " after waiting for 1 second.");
								setTimeout(function(){
					        		handleData(data).then(resolve);
								}, 1000);
							}else{
								dataSetReductionStatus.shift();
								dataSetReductionStatus.unshift(data.slice(reduction).byteLength);
								dataSetReductionStatus.unshift(data.slice(0, reduction).byteLength);
								
								// 各別以遞迴方式處理拆分後的資料
								// Handle first part of reduced data
								console.log("Split data");
								handleData(data.slice(0, reduction))
								.then(function(){
									// Handle second part of reduced data
									return handleData(data.slice(reduction));
								})
								.then(resolve);	
							}
					    }
					});
				}
			};		
	});
}

相關的說明已寫在程式碼的註解當中,這邊再做一些說明:

  1. 程式使用了 Javascript 的 fetch() 來進行影片資料的讀取,跟 XMLHttpRequest 不同,
    XMLHttpRequest 只能在讀取完全部資料後進行資料處理,
    但 fetch() 可以使用在 callback 函式中得到的 response 用 response.body.getReader() 來得到
    ReadableStream Reader,可以多次地使用 ReadableStream Reader 的 read() 來讀取部份資料,不用一次全部讀完。
  2. 當進行 sourceBuffer.appendBuffer() 把一段影片資料放到 sourceBuffer 時,
    可能會因為 Buffer 已滿而碰到 QuotaExceededError,
    這時程式會進行資料拆分,例如以 byte 為單位,先處理前 80% 的資料,再處理後 20% 的資料,
    而如果處理各拆分的資料還是碰到 QuotaExceededError 的話,就再繼續遞迴地進行拆分處理,
    最後如果要被處理的資料只有 1 byte,則不再進行拆分,當碰到 QuotaExceededError 時,
    則等待 1 秒鐘讓 Buffer 有空間時再遞迴地進行處理。
  3. 程式使用了 Promise 來處理非同步的影片處理,
    handleData() 會回應一個 Promise 物件,
    當它被 resolve() 時代表一段由 readNextDataset() 讀取到的影片資料被處理完畢,
    因為在處理影片時,有可能會碰到 QuotaExceededError 而需要進行影片資料的拆分,
    還有在 sourceBuffer.appendBuffer() 時需要等待 updateend 事件發生後才行進行下一次的 sourceBuffer.appendBuffer(),
    這些都是非同步的行為,所以使用 Promise 可以較好的處理流程。
  4. 程式中為了方便地觀察每一次由 readNextDataset() 讀取到的影片資料,
    在 sourceBuffer.appendBuffer() 時,資料是被拆分成何種狀態,
    所以建立了一個名為 dataSetReductionStatus 的 Arry 物件來觀察,
    例如一開始要處理的資料是 1024 byte 時,
    dataSetReductionStatus = [1024]
    拆分成 819 byte 和 205 byte 時,則是
    dataSetReductionStatus = [819, 205]。


最後下面是程式執行的樣子,
normal_video.mp4 會等影片全部讀取完才會開始播放,
moov_moved_video.mp4 會在讀到 moov 時就會開始播放,
fragmented_video.mp4 會在影片檔一邊下載一邊一段段的播放各個 fragment (mdat),
連覽器的 console 則是會列印出一些 debug 訊息。



原始碼分享:

  1. readableStreamTest.zip (內含範例使用的三個 MP4 影片檔)

資原分享:

  1. Bento4-SDK-1-6-0-639.x86_64-microsoft-win32.zip
  2. ffmpeg.zip
  3. 在這邊可以免費下載容量較大片長較長的影片做測試用:
    https://www.learningcontainer.com/mp4-sample-video-files-download/
    我範例使用的影片是:
    https://jsoncompare.org/LearningContainer/SampleFiles/Video/MP4/Sample-Video-File-For-Testing.mp4

參考資料:

  1. 5分钟入门MP4文件格式
  2. MediaSource错误:Failed to execute"endOfStream" on "MediaSource": The MediaSource‘s readyState is not “open”.
  3. Exceeding the buffering quota
  4. 4.22 mov, mp4, ismv
  5. JavaScript Promise 全介紹

2017年2月18日 星期六

WebSocket - Tomcat v8.0+ - 簡單的聊天室

WebSocket可以在伺服器和瀏覽器之間建立雙向通訊,今天要來用
WebSocket來實現一個簡單的聊天室做範例,紀錄一下如何使用WebSocket
伺服器採用Tomcat v8.0+、JDK 1.8,IDE 為 NetBeans

需求:

  1. 網頁上面可以輸入名稱登入聊天室。 登入後聊天室會出現登入訊息。
  2. 登入後可在訊息框中輸入訊息並送出,送出後聊天室會出現訊息。
  3. 聊天室出現訊息指的是,任何已登入聊天室的人都會看到訊息。
  4. 登入後的人只能看到登入後有人打的訊息。


先來看一下檔案結構:


2015年6月27日 星期六

Struts簡單入門 - 簡易身份認證(使用NetBeans)

Struts是一個基於MVC(Model-View-Control)架構的Java Web框架,今天要以一個簡單的身份認證例子來初步地了解Struts的MVC概念精神。

下圖為一個Struts的簡單原理流程圖,其中在web.xml中會設定什麼樣的url要交由ActionServlet來轉發給處理邏輯(例如 *.do),JSP File相當於View的可視化介面,當JSP File要把表單(Form)中的參數傳給商業邏輯處理時,ActionServlet會去struts-config.xml中尋找對應Form裡指定目標url的Action(相當於Control)和相應要物件實例化Form的Bean類別,ActionForm(相當於Model)。之後在Action中處理商業邏輯,並指定一個轉送目標url,這時就會轉送到struts-config.xml中對應轉送標的JSP或Action等。

我們的範例專案檔案配置如下:

  1. login.jsp                  : 身份認證的輸入畫面。
  2. loginSuccess.jsp     : 認證成功的畫面。
  3. struts-config.xml    : Struts的配置設定。
  4. LoginCheckAction : 處理身份認證商業邏輯的Action。
  5. LoginFormBean     : 用來實例物件Form的Bean類別。



以下為程式碼,相關的解釋都在註解裡:

2014年9月25日 星期四

關於限制存取根目錄URL與welcome-file的問題記錄

這裡記錄了一個我曾經發生但不太懂的開發情況:
以下是JSP的安全權限設定:
<security-constraint>
        <display-name>ManagerConstraint</display-name>
        <web-resource-collection>
            <web-resource-name>iBeaconServerPages</web-resource-name>
            <description>Pages of iBeacon Project Server</description>
            <url-pattern>/*</url-pattern>
        </web-resource-collection>
        <auth-constraint>
            <description/>
            <role-name>Manager</role-name>
        </auth-constraint>
    </security-constraint>
    <security-constraint>
        <display-name>TicketCheckerConstarint</display-name>
        <web-resource-collection>
           <web-resource-name>PagesAboutTicketChecker</web-resource-name>
            <description>Pages about Ticket Checker</description>
            <url-pattern>/welcomePage.jsp</url-pattern>
            <url-pattern>/ticketCheckUsers/*</url-pattern>
            <url-pattern>/guestFlowMonitor/*</url-pattern>
            <url-pattern>/logoutPage.jsp</url-pattern>
        </web-resource-collection>
        <auth-constraint>
            <description/>
            <role-name>TicketChecker</role-name>
            <role-name>Manager</role-name>
        </auth-constraint>
    </security-constraint>
可以看到,在根目錄下的所有頁面將限制由Manager存取,而/welcomePage.jsp、/ticketCheckUsers/*、/guestFlowMonitor/*和logoutPage.jsp限制由Manager和TicketChecker存取,以上四個頁面在url匹配時會先在TicketCheckerConstarint的權限設定中被匹配到,所以如果不設Manager的話,Manager將無法存取。

2014年9月8日 星期一

Linux的重要相關設定、指令、好用指令

重要路徑:
Tomcat:
(1)權限設置的位置(角色、使用者、密碼等):
/usr/share/tomcat6/conf/tomcat-users.xml

(2)部署war檔的地方:
/var/lib/tomcat6

Apache:
(1)部署網頁的地方(包括php):
/var/www/html

重要指令:
(1)重啟Apache:   /etc/init.d/httpd restart
(2)重啟MySQL:  /etc/init.d/mysqld restart
(3)重啟Tomcat:  /etc/init.d/tomcat6 restart
(4)進入MySQL: mysql -u root -p (root可改不同user名稱)
(5)新增使用者帳號:useradd 帳號名稱
(6)設定使用者的密碼:passwd 帳號名稱

重要設定:
(1)設定DNS(重開機後要重設)
進入 /etc/resolv.conf
vi /etc/resolv.conf
打入以下內容
nameserver 8.8.8.8
nameserver 8.8.4.4

(2) 第一次設定 root 密碼:
sudo passwd root

好用指令:
得到上次執行的命令或角本返回的狀態值 (0 為正常,非 0 為不正常,非 0 的狀態碼由角本作者自行決定意義)
echo $?

列出所有儲存裝置資訊,包括塊磁碟分區資訊等 (list block device):
lsblk
加上 -f 參數可列出一些其他資料,例如檔案格式 (FSTYPE),如值為空則代表還未格式化,尚不具有檔案系統。

格式化區塊成檔案系統
mkfs -t <檔案格式> <要被格式化的區塊>
範例:
mkfs -t xfs /dev/xvdf

備份檔案 rsync 指令,比 cp (複制) 更多功能的 rsync 指令,像是可以顯示多檔案進度、可判斷有異動的檔案才複制等功能
範例 (-P 等同於 --progress (可顯示進度)和 --partial (保留傳輸中斷的檔案以進行續傳)、
-a 等同於 --archive,為 archive mode,等同於使用了參數 -rlptgoD +  沒有 -H,-A,-X):
rsync -aP <來源路徑> <目標路徑>

在Centos 6下安裝phpMyAdmin及Tomcat

情境:
1.系統為Centos 6
2.已裝MySQL
3.已裝php
4.已裝php-mbstring

需求:
1.欲安裝phpMyAdmin
2.欲安裝Tomcat

解法過程:
一、欲安裝phpMyAdmin
        參考資料:CentOS 6.4 (64位元) 安裝 phpMyAdmin

(1).先下載 phpMyAdmin 的壓縮檔
wget http://downloads.sourceforge.net/project/phpmyadmin/phpMyAdmin/3.5.8.1/phpMyAdmin-3.5.8.1-all-languages.tar.gz

(2)對其進行解壓縮
tar -zxvf phpMyAdmin-3.5.8.1-all-languages.tar.gz

(3)將解壓縮後的 phpMyAdmin-3.5.8.1-all-languages 目錄移到 Apache 預設的網頁目錄下並重新命名為 phpMyAdmin
mv phpMyAdmin-3.5.8.1-all-languages /var/www/html/phpMyAdmin

(4)回到新放置的目綠位置
cd /var/www/html/phpMyAdmin
(5)複製範本設定檔 config.sample.inc.php 並重新命名為 config.inc.php
cp config.sample.inc.php config.inc.php
(6)進入 config.inc.php文件中修改認證操作方式
vi config.inc.php

將 $cfg['Servers'][$i]['auth_type'] = 'cookies'; 的 'cookies' 改成 'http' ,如下所示
$cfg['Servers'][$i]['auth_type'] = 'http';
(7)接著重新啟動Apache伺服器應該就能成功以localhost/phpMyAdmin為網址用瀏覽器登入phpMyAdmin了
service httpd restart

2014年8月6日 星期三

如何使用JSP的安全機制功能-以tomcat及netbeans為例

JSP的安全機制功能,可以很簡單地用宣告式的方法來進行設定,如以下步驟:

1.Tomcat中設定Role(角色)、身份(User)與密碼(Password)(採宣告方式,與程式獨立)
TomcatCatalinaHome/conf/tomcat-users.xml<tomcat-users>的標籤中,設定<user><roles>的內容,如以下所示:
<role rolename="Manager"/>
<user username="Hugo" roles="Manager" password="12345"/>

</tomcat-users>

意涵為有一個角色為Manager,有一個身份為Hugo,其角色為Manager,密碼為12345

其中CatalinaHome的位置可以在netBeans中Server按右鍵選擇Properties查得,如下圖所示:
2.之後在netBeans中打開web.xml,加入如以下標籤內容:

在JSP及Servlet之間傳送中文參數

在開發JSP、Servlet動態網頁時,常常會用到參數在頁面之間進行傳遞,例如在把一個JSP中的表單(Form)中的參數值傳給要處理的Servlet時,在Servlet端通常會使用如
request.getParameter("參數名值對中的名")

的指令去取傳進來的參數。在英文的環境下這些都是沒有問題的,不過如果現在要傳的參數值是中文時,就會發生得不到正確參數值的情況,遇到這種情況時,就必須要考慮到送收雙方的編碼一致問題,以下是兩種此問題發生的解法,分為參數傳送時是用GET還是POST,兩種情況的處理方式不相同,無法混用:

一、參數傳送是用GET時
假設送收雙方都決定要以UTF-8送收
1.送方因為是想要讓中文參數能利用URL傳遞,所以要進行遍碼,例如要傳送一個名為"requestAction"、值為"修改"的參數時,要用呼叫URLEncoder.encode()方法來進行參數的編碼,如下例所示

網址/request=<%= URLEncoder.encode("修改", "UTF-8")%>

2.收端要使用一樣的編碼來進行解碼,如下所示

String requestAction = new String(request.getParameter("requestAction").getBytes("ISO-8859-1"), "UTF-8");

上面程式碼的意思是將收到的參數從「ISO-8859-1」轉成「UTF-8」,如果伺服器的預設解碼不是「ISO-8859-1」的話要記得改成相應的編碼。
如果是Tomcat,可以在CatalinaHome/conf/server.xml中找到如下列字樣
<Connector URIEncoding="utf-8" connectionTimeout="20000" port="8080" protocol="HTTP/1.1" redirectPort="8443"/>
此列表示預設編碼為「UTF-8」,如果沒有「URIEncoding="utf-8"」的話也可自己加入。
要注意的是如使用NetBeans的IDE來開發的話,它是用模擬的Tomcat Server來Run模擬結果的,所以這裡用的server.xml也是模擬的,可以在NetBeans中的Servers對Tomcat按右鍵選「Edit server.xml」來更改設置,如下圖:


二、參數傳送是用POST時
假設送收雙方都決定要以UTF-8送收
1.送方(通常為JSP)要設定用UTF-8送出資料,如加上以下程式碼(以JSP為例):
<%@page contentType="text/html" pageEncoding="UTF-8"%>

2.收方(通常為Servlet)要設定用UTF-8解碼接收的資料,如在取值之前加上以下程式碼(以Servlet為例):
 request.setCharacterEncoding("UTF-8"); //中文參數的解碼
解完碼後,再進行取值,如以下程式碼:
String requestParam = request.getParameter("gotParam");
這樣就可以正確地得到中文參數了

附錄:
在這篇Tomcat與UTF-8編碼中,有講到如何設定Tomcat可以對Get及Post的參數使用自訂如UTF-8的編碼來進行解碼。

2014年6月14日 星期六

在Linux下使用Tomcat、JSP連Mysql資料庫(配合Netbeans IDE)

在Linux下如果要使用及開發JSP動態網頁的話,通常會選擇使用Tomcat來作為JSP的伺服器,以下介紹如何在Linux的環境下安裝Tomcat

1.到Apache Tomcat的官網上下載Tomcat,下載下來後將個資料夾解壓縮後放到要配置的路徑下,這裡選擇/usr/share/java下,路徑可以自選,為了方便起見,以下用 $CATALINA_HOME  稱呼

補充:有人說還要再進行環境變數等設置,不過我沒有設就直接成功了,需要設的人可以參考這篇文這篇文

2.在命令列指令界面中到$CATALINA_HOME/bin下,打上以下指令開啟動Tomcat,
./startup.sh
及使用以下指令關閉Tomcat
./shutdown.sh
這時可能會發現有錯誤發生,通常為對資料夾權限不夠的關係,這時可參考這篇文的作法,回到$CATALINA_HOME路徑上一層的路徑中,打上以下指令開放權限
 
sudo chmod 755 -R 你的Tomcat資料夾名稱/*

3.啟動Tomcat後,打開瀏覽器在網址列打上 http://localhost:8080  就可連上Tomcat的預設管理頁面

接著要介紹如何簡單地配合Netbeans IDE來建立能連上Mysql資料庫的JSP網頁