2009-09-29 70 views
1

我有一個我想從J2EE Web應用程序觸發的shell腳本。從Java觸發shell腳本的最佳方法

該腳本處理很多事情 - 處理,FTP等 - 這是一個遺留問題。

運行需要很長時間。

我想知道什麼是最好的方法。我希望用戶能夠點擊鏈接,觸發腳本,並向用戶顯示一條消息,說明腳本已啓動。我希望HTTP請求/響應週期是即時的,無論我的腳本需要很長時間才能運行。

我能想到的三個選項:

  • 產生新線程的用戶點擊的處理過程中。但是,我認爲這不符合J2EE規範。
  • 在觸發腳本之前,向HTTP響應流發送一些輸出並提交它。這給出了HTTP請求/響應循環已經完成的錯覺,但實際上,處理請求的線程仍然在那裏等待shell腳本完成。所以我基本上爲了自己的目的劫持了容器HTTP處理線程。
  • 創建一個包裝腳本,在後臺啓動我的主腳本。這將使請求/響應循環在容器中正常完成。

以上所有將使用servlet和Runtime.getRuntime()。exec()。

這是在Solaris上使用Oracle的OC4J應用程序服務器在Java 1.4.2上運行的。

請問沒有人有任何意見哪個是最簡單的解決方案,爲什麼?

或者沒有人有更好的方法嗎?我們已經有了Quartz,但我們不想將shell腳本重新實現爲Java過程。

謝謝。

回答

2

我會選擇3,特別是如果你實際上並不需要知道腳本何時結束(或者有其他方法來尋找,而不是等待進程結束)。

選項1浪費了一個線程,它只是坐在等待腳本完成。選項2似乎是一個壞主意。我不會劫持servlet容器線程。

+0

另一個想法是使用單獨的處理線程,該線程在隊列中等待(或用於Object.notify()+ Object.wait())的通知,並且具有HTTP請求處理程序推入隊列(或調用通知)。 – ash 2013-08-30 06:51:20

0

對於第二個選項,您可以使用servlet,並且在您響應HTTP請求之後,可以使用java.lang.Runtime.exec()來執行腳本。我也建議你看看這裏:http://www.javaworld.com/javaworld/jw-12-2000/jw-1229-traps.html

...對於使用它的一些問題和缺陷。

+0

謝謝,但我假設你會意識到我會使用java.lang .Runtime.exec()在我上面提出的所有解決方案中。此外,我還假設他們都需要使用servlet(或類似的)。 – 2009-09-29 16:36:38

+0

我編輯了我的問題來澄清。 – 2009-09-29 16:37:45

1

我對這種做法很可能是類似以下內容:

  • 搭建ExecutorService的servlet中執行的實際執行。
  • 使用適當的返回類型創建Callable的實現,該實現包裝實際的腳本執行(使用Runtime.exec())將Java輸入變量轉換爲shell腳本參數,並將腳本輸出到適當的Java對象。
  • 當請求進入時,創建一個合適的Callable對象,將其提交給執行者服務,並將生成的Future放在某個持久的地方(例如,用戶的會話或UID鍵控映射,將該鍵返回給用戶供以後查找,具體取決於要求)。然後立即向用戶發送一個HTTP響應,意味着該腳本已經開始正常(包括查找關鍵字,如果需要的話)。
  • 爲用戶添加一些機制來輪詢他們任務的進度,根據您剛剛查看的Future的狀態返回「仍在運行」響應,「失敗」響應或「成功+結果」響應向上。

這是一個有點handwavy但根據您的web應用程序是如何組織的,你可以大概在什麼地方適合這些通用部件。

+0

對不起 - 忘了說,它在1.4.2 JRE中,所以併發包默認是不可用的。 – 2009-09-29 17:17:52

+0

有一個可用於Java 1.4的JSR 166(java.util.concurrent)的回送。請參閱http://backport-jsr166.sourceforge.net/ – 2009-09-29 18:18:07

1

如果您的HTTP響應/用戶不需要查看腳本的輸出,或者知道腳本何時完成,那麼您最好的選擇是在您提到的某種包裝腳本中啓動線程它可以作爲一個整體在servlet容器環境之外運行。這意味着你可以免除需要管理容器內的線程,或者像你提到的那樣劫持線程等。

只有當用戶需要被告知何時腳本完成和/或監視腳本的輸出我考慮選擇1或2

2

是否需要爲您的應用程序評估您開始的腳本的輸出,或者這是一個簡單的消防工作?如果不需要,您可以'濫用'Runtime.getRuntime()。exec()將立即返回並繼續在後臺運行該進程的事實。如果您真的想等待腳本/進程完成,您將不得不在exec()返回的Process對象上調用waitFor()。

如果您正在開始的進程向stdout或stderr寫入任何內容,請確保將它們重定向到日誌文件或/ dev/null,否則該進程將在一段時間後阻塞,因爲stdout和stderr可用作InputStreams通過Process對象有限的緩衝能力。

+0

我很擔心JavaDocs中關於緩衝區可能阻塞的進程的聲明,所以我並不想這麼做 - 以防萬一出現stdout/stderr。所以,如果你可以保證所有的stdout/stderr被重定向,那麼這個方法是否可以保證工作?但我想這將涉及修改shell腳本,這在理想情況下我不想做。 – 2009-09-30 07:07:53

0

異步後端進程最健壯的解決方案是使用消息隊列IMO。最近我使用Spring嵌入式ActiveMQ代理實現了這個功能,並安裝了一個生產和消費bean。當需要開始工作時,我的代碼會調用將消息放入隊列的生產者。消費者訂閱了隊列,並通過消息在單獨的線程中被踢入行動。這種方法將UI與排隊機制(通過生產者)以及異步處理(由消費者處理)整齊分開。

請注意,這是一個在開發人員計算機上的Tomcat服務器上運行的Java 5,Spring配置的環境,並已部署到測試/生產計算機上的Weblogic。

0

您的問題源於您試圖違背J2EE中的'單響應每個請求'模型,並且在後端任務執行時最終用戶的頁面會動態更新。

除非您想要介紹基於Ajax的解決方案,否則您必須強制用戶瀏覽器上的渲染頁面定期輪詢服務器以獲取信息,直到後端任務完成。

這可以通過以下方式實現:

  1. 當J2EE容器接收該請求,生成一個線程這需要給會話對象的引用(其將被用來編寫腳本的輸出)

  2. 初始化響應servlet以寫入一個html頁面,該頁面將包含一個Javascript函數,以定期(每10秒左右)從服務器重新加載頁面。

  3. 在每個請求,輪詢會話對象來顯示由產生的線程在步驟存儲在輸出1

  4. [可加入清理邏輯刪除從會話中存儲的內容一旦線程完成如果需要的話,也可以設置在會話中的任何其他標誌爲腳本的執行]

這是爲了實現你想要的一種方式的標記狀態轉換 - 這是不是最優雅的所有方法,但實質上是由於需要異步更新服務器中的頁面內容r,帶有請求/響應模型。

還有其他方法可以實現這個目標,但這取決於您的約束條件如何不靈活。我聽說過Direct Web Remoting(雖然我還沒有玩過它),可能值得看看Developing Applications using Reverse-Ajax

相關問題