2014-05-01 50 views
0

有一個網址列表,如:查找列表中的適當第一項通過檢查它只有一次,並返回檢查結果

val urls = List("http://....", "http://....", "http://....", "http://....", ...) 

某些網址無法訪問,有的人會返回狀態404500

而且我可以檢查一個網址可訪問,也有狀態代碼200,並獲得ContentType以及一個功能。該功能相當昂貴,因爲它將訪問網絡並且讀取超時時間爲10秒。

case class GoodSite(url:String, contentType: String) 

def checkUrl(url:String): Option[GoodSite] = { 
    // visit the url, and get the response status code and content type 
    if(responseCode==200) Some(GoodSite(url, contentType)) 
    else None 
} 

現在我想通過一個從列表中的一個檢查URL,並且只得到第一個將返回代碼200,然後返回內容類型。

我有兩個解決方案:

urls.flatMap(checkUrl).headOption 

urls.find(url => checkUrl(url)!=None).map(checkUrl(_)) 

但他們兩人都不好,因爲他們將執行checkUrl功能的一些不必要的電話。

有沒有什麼好的解決方案可以只調用一次?

+0

我認爲'find'是如何工作的。你如何確認它執行'checkUrl'的不必要調用? – Kabie

+0

你可以看到在第二個解決方案中有兩個'checkUrl'調用,但只有第一個足以獲得我想要的所有信息。 – Freewind

回答

1

Eugene Zhulenev's和Kai Sternad的答案都滿足您的原始要求。但我認爲你重新考慮你的問題。

想象一下,您的urls的長度爲N,第k個網址需要T(k)時間檢查,順序代碼查找GoodSite的時間是多少?在最好的情況下,如果第一個網址是好的,它將是T(0)。在最壞的情況下,它將是T(0) + T(1) + ... + T(N-1),對於T(N-1)是唯一的GoodSite和不存在GoodSite的情況。

我的建議是開始同時檢查所有的URL,異步,並在找到第一個GoodSite後立即檢查。在每個存在GoodSite的情況下,將需要min(T(0), T(1), ..., T(N-1)),如果不存在GoodSite,則時間爲max(T(0), T(1), ..., T(N-1))

以下是使用噴霧客戶端(1.3.1-20140423_2.11)演示我的觀點的一段工作代碼。

import akka.actor.ActorSystem 
    import scala.util.{Failure, Success} 
    import spray.http.HttpHeaders.`Content-Type` 
    import scala.concurrent.Future 
    import spray.http._ 
    import spray.client.pipelining._ 

    implicit val system = ActorSystem() 
    import system.dispatcher 

    val pipeline = sendReceive 
    case class GoodSite(url: String, contentType: Option[`Content-Type`]) 

    def url2GoodSite(url: String): Future[GoodSite] = { 
    pipeline(Get(url)).map { response => 
     response.status match { 
      case StatusCodes.OK => GoodSite(url, response.header[`Content-Type`]) 
      case _ => throw new RuntimeException("bad site") 
     } 
    } 
    } 

    val data = List("http://www.yahoo.com", "http://www.sina.com.cn", "http://www.oschina.net") 

    val result = Future.find(data.map(url2GoodSite))(_ => true) 

    result.onComplete { 
    case Success(good) => 
     // use the fastest GoodSite 
     println(good) 
     system.shutdown() 
    case Failure(e) => 
     // no GoodSite found 
     println("No good site found.") 
     system.shutdown() 
    } 

使用噴霧客戶端或其他庫並不重要。 def url2GoodSite(url: String): Future[GoodSite]的實現細節並不重要。關鍵是它返回Future[GoodSite]而不是Option[GoodSite]。無論何時處理網絡io,都要考慮這種轉變。考慮這是追趕reactive趨勢的第一步。

+0

感謝您爲我提供一些新想法的優秀解決方案! – Freewind

2

後您從列表採取迭代器的所有操作都懶的評價:

val urls = List("http://google.com", "http://amazon.com", "http::/yahoo.com") 

    case class GoodSite(url:String, contentType: String) 

    val cnt = new AtomicInteger(0) 

    def checkUrl(url:String): Option[GoodSite] = { 
    println(s"Check url: $url") 
    val (responseCode, contentType) = getResponseCodeAndContentType(url) 
    if(responseCode==200 && cnt.getAndIncrement == 1) 
     Some(GoodSite(url, contentType)) 
    else None 
    } 

    val firstGood = urls.iterator.map(checkUrl).collectFirst { 
    case Some(good) => good 
    } 

    println(firstGood) 
+0

'cnt'不是必需的,因爲這是一個並行的迭代器。 – xiefei

+0

@xiefei,你是說我們不需要'AtomicInteger',實際上我們需要一個'MutableInteger'或者'var'? – Freewind

2

您也可以將您的URL列表轉換成流,然後下降,而你沒有一個很好的網站:

urls.toStream.map(checkUrl).dropWhile(_.isEmpty).headOption 
相關問題