2015-01-07 3 views
0

Я пишу простой (общий) оболочный Java-класс, который будет выполняться на разных компьютерах отдельно от развернутого веб-сервера. Я хочу загрузить последнюю версию файла jar, который является приложением с этого связанного веб-сервера (в настоящее время Jetty 8).Интеллектуально обслуживающие файлы jar с веб-сервера

У меня есть такой код:

// Get the jar URL which contains the application 
URL jarFileURL = new URL("jar:http://localhost:8081/myapplication.jar!/"); 
JarURLConnection jcl = (JarURLConnection) jarFileURL.openConnection(); 

Attributes attr = jcl.getMainAttributes(); 
String mainClass = (attr != null) 
      ? attr.getValue(Attributes.Name.MAIN_CLASS) 
      : null; 
if (mainClass != null) // launch the program 

Это работает хорошо, за исключением того, что myapplication.jar большая баночка файл (OneJar jarfile, так много находится там). Я хотел бы, чтобы это было максимально эффективно. Файл jar не будет меняться очень часто.

  1. Может ли файл jar быть сохранен на диск (я вижу, как получить объект JarFile, но не для его сохранения)?
  2. Что еще более важно, но связанное с №1, может ли файл jar быть кэширован каким-то образом?

    2.1 Могу ли я (легко) запросить MD5 файла jar на веб-сервере и загрузить его только после того, как это изменилось?
    2.2 Если нет другого механизма кэширования, может потребоваться только манифест? Информация о версии/сборке может быть сохранена там.

Если кто-нибудь сделал что-то подобное, вы могли бы набросать на детали, что делать дальше?

ОБНОВЛЕНИЕ НА ПЕРВЫХ ОТВЕТЫ

Предложение заключается в том, чтобы использовать If-Modified-Since заголовка в запросе и методе Openstream на URL, чтобы получить файл банка, чтобы сохранить.

Основываясь на этой обратной связи, я добавил одну важную часть информации и некоторые более сфокусированные вопросы.

Программа java, описанная выше, запускает программу, загруженную из файла jar, на который делается ссылка. Эта программа будет работать от 30 секунд до 5 минут или около того. Затем выполняется и завершается. Некоторые пользователи могут запускать эту программу несколько раз в день (скажем, до 100 раз), другие могут запускать ее нечасто, как раз в неделю. Он должен быть достаточно умным, чтобы знать, имеет ли он самую последнюю версию файла jar.

более сфокусированные Вопросы:

Будет ли If-Modified-Since заголовок все еще работает в этом использовании? Если да, мне нужен совершенно другой код, чтобы добавить это? То есть, можете ли вы показать мне, как изменить представленный код, чтобы включить это? Тот же вопрос в отношении сохранения файла jar - в конечном счете, я действительно удивлен (разочарован!), Что могу получить объект JarFile, но не могу его сохранить - мне даже понадобится класс JarURLConnection?

Bounty Вопрос

Я сначала не понимаю, точный вопрос, который я пытался спросить. Это:

Как сохранить файл jar с веб-сервера локально в программе командной строки, которая выходит и ТОЛЬКО обновляет этот файл jar, когда он был изменен на сервере?

Любой ответ, который, по примерам кода, показывает, как это может быть сделано, будет присуждена награда.

+2

Вы можете использовать ' If-Modified-Since' в вашем запросе GET, и сервер ответит 304, если файл не изменился. – fge

+0

Почему бы просто не использовать DefaultServlet для обслуживания файлов с сервера Jetty, а затем на стороне клиента пройти в текущем временная метка lastModified файловой системы как заголовок HTTP-запроса 'If-Modified-Since'. Нет смысла изобретать колесо на этом. –

+0

@Joakim Erdfelt - это звучит хорошо и хорошо, но я совершенно не знаю, как это сделать! У вас есть указатель на статью или другой вопрос? Благодаря! – JoeG

ответ

2
  1. Да, файл можно сохранить на диск, вы можете получить входной поток, используя метод openStream() в классе URL.

  2. В соответствии с комментарием, указанным @fge, существует способ определить, был ли файл изменен.

Пример код:

private void launch() throws IOException { 
    // Get the jar URL which contains the application 
    String jarName = "myapplication.jar"; 
    String strUrl = "jar:http://localhost:8081/" + jarName + "!/"; 

    Path cacheDir = Paths.get("cache"); 
    Files.createDirectories(cacheDir); 
    Path fetchUrl = fetchUrl(cacheDir, jarName, strUrl); 
    JarURLConnection jcl = (JarURLConnection) fetchUrl.toUri().toURL().openConnection(); 

    Attributes attr = jcl.getMainAttributes(); 
    String mainClass = (attr != null) ? attr.getValue(Attributes.Name.MAIN_CLASS) : null; 
    if (mainClass != null) { 
     // launch the program 
    } 
} 

private Path fetchUrl(Path cacheDir, String title, String strUrl) throws IOException { 
    Path cacheFile = cacheDir.resolve(title); 
    Path cacheFileDate = cacheDir.resolve(title + "_date"); 
    URL url = new URL(strUrl); 
    URLConnection connection = url.openConnection(); 
    if (Files.exists(cacheFile) && Files.exists(cacheFileDate)) { 
     String dateValue = Files.readAllLines(cacheFileDate).get(0); 
     connection.addRequestProperty("If-Modified-Since", dateValue); 

     String httpStatus = connection.getHeaderField(0); 
     if (httpStatus.indexOf(" 304 ") == -1) { // assuming that we get status 200 here instead 
      writeFiles(connection, cacheFile, cacheFileDate); 
     } else { // else not modified, so do not do anything, we return the cache file 
      System.out.println("Using cached file"); 
     } 
    } else { 
     writeFiles(connection, cacheFile, cacheFileDate); 
    } 

    return cacheFile; 
} 

private void writeFiles(URLConnection connection, Path cacheFile, Path cacheFileDate) throws IOException { 
    System.out.println("Creating cache entry"); 
    try (InputStream inputStream = connection.getInputStream()) { 
     Files.copy(inputStream, cacheFile, StandardCopyOption.REPLACE_EXISTING); 
    } 
    String lastModified = connection.getHeaderField("Last-Modified"); 
    Files.write(cacheFileDate, lastModified.getBytes()); 
    System.out.println(connection.getHeaderFields()); 
} 
+0

Это именно то, что я искал - ОБЫЧНО Я не понимал «If-Modified-Since»! Соответственно, я даю это щедрость! Я все еще получаю несколько исключений, когда запускаю его в TestCase. Как только я уберу это, я опубликую то, что мне нужно было сделать. – JoeG

0

я могу поделиться опытом решения тех же задач в нашей команде. У нас есть несколько настольных продуктов, написанных в java, которые регулярно обновляются.

Несколько лет назад у нас был отдельный сервер обновлений для каждого продукта и следующий процесс обновления: клиентское приложение имеет обертку обновления, которая начинается до основной логики и сохраняется в udpater.jar. Перед запуском приложение отправляет запрос на обновление сервера с MD5-хешем файла application.jar. Сервер сравнивает полученный хэш с тем, который у него есть, и отправляет новый файл jar в updater, если хеши разные.

Но после многих случаев, когда мы путали, какая сборка сейчас находится в производстве, и сбои обновления-сервера, мы перешли на continuous integration практику с TeamCity поверх нее.

Каждое совершение, совершенное разработчиком, теперь отслеживается сервером сборки. После компиляции и прохождения теста сервер сборки присваивает номер сборки приложению и распределяет распределение приложений в локальной сети.

Сервер обновлений в настоящее время является простой веб-сервер с особой структурой статических файлов:

$WEB_SERVER_HOME/ 
    application-builds/ 
     987/ 
     988/ 
     989/ 
      libs/ 
      app.jar 
      ... 
      changes.txt <- files, that changed from last build 
     lastversion.txt <- last build number 

Updater на стороне клиента запросов lastversion.txt через HttpClient, извлекает последний номер сборки и сравнивает его с номером клиента сборки, хранящегося в manifest.mf. Если требуется обновление, программа обновления собирает все изменения, сделанные после последнего обновления, итерации по файлам-builds/$ BUILD_NUM/changes.txt. После этого программа updater загружает собранный список файлов. Могут быть jar-файлы, файлы конфигурации, дополнительные ресурсы и т. Д.

Эта схема кажется сложной для клиентского обновления, но на практике она очень четкая и надежная.

Существует также сценарий bash, который формирует структуру файлов на сервере обновлений. Запросить скрипт TeamCity каждую минуту, чтобы получить новые сборки и рассчитать разницу между сборками. Мы также модернизируем это решение для интеграции с системой управления проектами (Redmine, Youtrack или Jira). Цель состоит в том, чтобы менеджер продукта мог отметить сборку, которая одобрена для обновления.

ОБНОВЛЕНИЕ.

Я переместил нашу программу обновления на GitHub, проверьте здесь: github.com/ancalled/simple-updater

Проект содержит программы обновления-клиент на Java, скрипты Баш на стороне сервера (получает обновления от сборки-сервера) и пример приложения для проверки обновлений на нем ,

+0

Это, безусловно, похоже на то, что мне нужно. Если бы вы могли поделиться чем-нибудь на репозитории github, который бы помог многим! – JoeG

2

Как сохранить файл jar с веб-сервера локально в программе командной строки, которая выходит и ТОЛЬКО обновляет этот файл jar, когда он был изменен на сервере?

С JWS. Он имеет API, поэтому вы можете управлять им из вашего существующего кода. У него уже есть управление версиями и кеширование, и он поставляется с сервлетом, обслуживающим JAR.

+0

из ссылки JWS: «технология развертывания приложений, которая дает вам возможность запускать полнофункциональные приложения одним щелчком мыши из вашего веб-браузера». У меня нет ссылки в браузере. Учитывая тот факт, что JWS не набирает обороты после стольких лет доступности, я тоже несколько подозреваю. – JoeG

+0

* Непрерывный. * Вам не нужен браузер. Как я уже сказал, у него есть API. Продолжай читать. – EJP

+0

Извините, хотя, по крайней мере, для меня это похоже на высказывание: вот ссылка на Java: http://docs.oracle.com/javase/8/docs/api/index.html прочитайте это и скажите мне, что вы думаете , Часто задаваемые вопросы - с марта 2006 года, и это было единственное время, когда я пытался его использовать. В чем разница между DownloadService и DownloadService2? Нужен ли мне слушатель? Когда мне нужно использовать ExtendedService и BasicService? Является ли PersistenceService единственным, кто будет экономить банки на местном уровне? Итак, хотя я использовал JWS крошечный бит МНОГО лет назад, общий указатель на чтение API (старый? Устаревший?) На самом деле не помогает – JoeG

1

Я предположил, что файл .md5 будет доступен как локально, так и на веб-сервере. Та же логика будет применяться, если вы хотите, чтобы это был файл управления версиями.

URL-адреса, указанные в следующем коде, необходимо обновить в соответствии с расположением вашего веб-сервера и контекстом приложения. Вот как ваша команда строка кода будет идти

public class Main { 

public static void main(String[] args) { 
    String jarPath = "/Users/nrj/Downloads/local/"; 
    String jarfile = "apache-storm-0.9.3.tar.gz"; 
    String md5File = jarfile + ".md5"; 

    try { 
     // Update the URL to your real server location and application 
     // context 
     URL url = new URL(
       "http://localhost:8090/JarServer/myjar?hash=md5&file=" 
         + URLEncoder.encode(jarfile, "UTF-8")); 

     BufferedReader in = new BufferedReader(new InputStreamReader(
       url.openStream())); 
     // get the md5 value from server 
     String servermd5 = in.readLine(); 
     in.close(); 

     // Read the local md5 file 
     in = new BufferedReader(new FileReader(jarPath + md5File)); 
     String localmd5 = in.readLine(); 
     in.close(); 

     // compare 
     if (null != servermd5 && null != localmd5 
       && localmd5.trim().equals(servermd5.trim())) { 
      // TODO - Execute the existing jar 
     } else { 
      // Rename the old jar 
      if (!(new File(jarPath + jarfile).renameTo((new File(jarPath + jarfile 
        + String.valueOf(System.currentTimeMillis())))))) { 
       System.err 
         .println("Unable to rename old jar file.. please check write access"); 
      } 
      // Download the new jar 
      System.out 
        .println("New jar file found...downloading from server"); 
      url = new URL(
        "http://localhost:8090/JarServer/myjar?download=1&file=" 
          + URLEncoder.encode(jarfile, "UTF-8")); 
      // Code to download 
      byte[] buf; 
      int byteRead = 0; 
      BufferedOutputStream outStream = new BufferedOutputStream(
        new FileOutputStream(jarPath + jarfile)); 

      InputStream is = url.openConnection().getInputStream(); 
      buf = new byte[10240]; 
      while ((byteRead = is.read(buf)) != -1) { 
       outStream.write(buf, 0, byteRead); 
      } 
      outStream.close(); 
      System.out.println("Downloaded Successfully."); 

      // Now update the md5 file with the new md5 
      BufferedWriter bw = new BufferedWriter(new FileWriter(md5File)); 
      bw.write(servermd5); 
      bw.close(); 

      // TODO - Execute the jar, its saved in the same path 
     } 

    } catch (IOException e) { 
     e.printStackTrace(); 
    } 
} 
} 

И только в случае, если вы имели контроль над кодом сервлета, а это, как код сервлета идет: -

@WebServlet(name = "jarervlet", urlPatterns = { "/myjar" }) 
public class JarServlet extends HttpServlet { 

private static final long serialVersionUID = 1L; 
// Remember to have a '/' at the end, otherwise code will fail 
private static final String PATH_TO_FILES = "/Users/nrj/Downloads/"; 

@Override 
protected void doGet(HttpServletRequest req, HttpServletResponse resp) 
     throws ServletException, IOException { 

    String fileName = req.getParameter("file"); 
    if (null != fileName) { 
     fileName = URLDecoder.decode(fileName, "UTF-8"); 
    } 
    String hash = req.getParameter("hash"); 
    if (null != hash && hash.equalsIgnoreCase("md5")) { 
     resp.getWriter().write(readMd5Hash(fileName)); 
     return; 
    } 

    String download = req.getParameter("download"); 
    if (null != download) { 
     InputStream fis = new FileInputStream(PATH_TO_FILES + fileName); 
     String mimeType = getServletContext().getMimeType(
       PATH_TO_FILES + fileName); 
     resp.setContentType(mimeType != null ? mimeType 
       : "application/octet-stream"); 
     resp.setContentLength((int) new File(PATH_TO_FILES + fileName) 
       .length()); 
     resp.setHeader("Content-Disposition", "attachment; filename=\"" 
       + fileName + "\""); 

     ServletOutputStream os = resp.getOutputStream(); 
     byte[] bufferData = new byte[10240]; 
     int read = 0; 
     while ((read = fis.read(bufferData)) != -1) { 
      os.write(bufferData, 0, read); 
     } 
     os.close(); 
     fis.close(); 
     // Download finished 
    } 

} 

private String readMd5Hash(String fileName) { 
    // We are assuming there is a .md5 file present for each file 
    // so we read the hash file to return hash 
    try (BufferedReader br = new BufferedReader(new FileReader(
      PATH_TO_FILES + fileName + ".md5"))) { 
     return br.readLine(); 
    } catch (IOException e) { 
     e.printStackTrace(); 
    } 
    return null; 
} 

} 
+0

Это действительно хорошо, просто код, предоставленный для первого ответа, фактически именно то, что я искал. Спасибо, и я поддержал! – JoeG

Смежные вопросы