אתר הטפסים הלאומי

קיבלתי דוח תנועה לפני מספר שבועות, ודרך אתר המשטרה הגעתי לאתר הטפסים הלאומי כדי למלט טופס ביטול דוח.
באתר הטפסים הלאומי, נדרשתי להוריד סרגל כלים כדי לקבל גישה לטופס, התחלה גרועה להמשך גרוע יותר.
ניסיתי להוריד את סרגל הכלים בפיירפוקס, והוא לא תומך בגרסאת הפיירפוקס שלי – הרי היא 6.0, עם זה אני מוכן לחיות, לפחות הם ניסו.
פתחתי את אינטרנט אקספלורר, וניכנסתי שוב.
התקנתי את סרגל הכלים, והתחלתי למלא את הטופס.
כמובן שהייתי צריך לנחש שאת מספר הדוח אני אמור להכניס בלי מקפים.
כשסיימתי חיפשתי כפתור אישור, שמירה, שליחה או כל דבר – אבל לא היה שום דבר.
באדום למעלה היה טקסט שאמר שאני אמור למלא ולהדפיס את הטופס.
די מטומטם, בשביל זה אני צריך סרגל כלים?
הדפסתי את המסך לקובץ, פתחתי אותו – ובפנים היו ההוראות המיוחלות:
אני צריך להדפס את הטופס דרך כפתור מזערי בסרגל הכלים.
השינאה שלי גדלה מרגע לרגע.
לחצתי על הכפתור המזערי, והדפסתי את הטופס לקובץ, כשפתחתי את הקובץ גיליתי שאת שדה הטקסט הקטן מדי בטופס הם הדפיסו בדיוק כמו שהוא נראה, מה שאומר שחצי מהטקסט שכתבתי לא מופיע בכלל בהדפסה.
השינאה גדלה עוד.
ערכתי את הטופס, מחקתי את כל האנטרים, והדפסתי שוב.
עכשיו אני אמור לשלוח את הטופס הזה בעצמי בדואר רשום, היעילות מעולם לא היתה כל כך נוחה.

אחרי החוויה המרנינה, החלטתי שאני חייב לשלוח להם משוב.
הרי יש להם חלק שלם במסך שמוקדש לשאלה "מה דעתך על תהליך מילוי הטופס?".
אבל אפילו החוויה עד כה לא הכינה אותי להמשך.
לחיצה על הכפתור המעוצב למשעי "שלח משוב" גורמת להודעת השגיאה הבאה:

לאחר מספר נסיונות, הבנתי שבמקביל להודעת השגיאה הוא גם פותח טאב חדש עם טופס המשוב.
הקלדתי בששון את המשוב החינני שלי, וכשסיימתי בהיתי במסך בחיפוש אחרי כפתור השליחה.
הוא לא היה, אפילו לא בסרגל הכלים המשוקץ.
עדכנתי את המשוב, ולבסוף שלחתי אותו דרך דף צור קשר.
עצוב לחשוב שמיטב כספי משלם המיסים (הכסף שאני משלם!!) הולך לזבל האורגני המתכנה ממשל זמין.
הנה המשוב שלי, להנאתכם.

ראשית, הרשו לי להתנצל מראש על הטון של המשוב.
הוא בא מהלב.

קודם כל, זה מטומטמם שאני נדרש להוריד סרגל כלים כדי להשלים משימה טריויאלית שבסופה אני עוד אמור להדפיס ולשלוח את הטופס בעצמי.
בתום מילוי הפרטים, לא היה ברור לי איך להמשיך. אין כפתור אישר או המשך או הדפס.
רק כשהדפסתי את הטופס כרגיל קיבלתי מהמדפסת שלי את הנחיה להדפיס דרך סרגל הכלים. דבר מוזר ולא סטנדרטי בעולם המחשבים.
כשהדפסתי את הטופס דרך סרגל הכלים, הטקסט שכתבתי לא הוצג כראוי ונאלצתי לעמד אותו בצורה אחרת ולהדפיס מחדש.
כשרציתי לשלוח משוב, קיבלתי שגיאה על כל שהטופס לא אומת.
לבסוף הצלחתי לשלוח משוב (אני מקווה).

בגדול, אני חושב שהמערכת הזו היא אחד הכישלונות הגדולים ביותר שיצא לי לראות מאתר ממשלתי.
חווית המשתמש מזוויעה וגרמה לי לרצות להרביץ למישהו.

בתקווה שתפטרו את מי שפיתח את זה ותתחילו מחדש, הפעם עם צוות שאינו מטומטם.

קבלו תיקון, מערכת שליחת המשוב שלכם היא באיכות דומה לשאר המערכת ואין אפשרות לשלוח דרכה משוב.
ללא ספק היא פותחה על ידי אותו חבר מפגרים שפיתח את השאר.
אני אנסה לשלוח את המשוב בצורה אחרת.

שינאה.

Facebook Comments

החלפת דיסק תקול בZFS

מכונת הOpenSolaris שלי, מכילה שישה דיסקים:
שני דיסקים של 1TB, שניים של 1.5TB ושניים של 2TB.
מאגר האחסון (pool) העיקרי בנוי בעצם משלושה זוגות של דיסקים (או חלקי דיסקים), כך שאם אחד מהם מתקלקל המערכת יכולה להמשיך לתפקד.
החסרונות של הגישה הזו הם שאני מאבד חצי מהנפח, ושאם שני דיסקים מאותו יחידת ראי נופלים איבדתי הכל.
היתרונות הם ביצועי קריאה כפולים, וגמישות בשינויי תצורה. קל להוסיף דיסקים או לשדרג דיסקים קיימים לגדולים יותר.

ההסתברות לכשלון של דיסק מסויים גדלה ביחס ישיר למספר הדיסקים, במהלך השנתיים בערך שיש לי את המערכת הזו, שלושה דיסקים כשלו (!) ובכל זאת עד היום לא איבדתי מידע.
בכשלון האחרון הבחנתי לפני כמספר שבועות : לא הרגשתי בעיות, אבל ראיתי במקרה שZFS מדווח על שגיאה בדיסק, מה שהביא את המאגר כולו למצב degraded, שהוא בהחלט לא מצב שתרצו להשאר בו יותר זמן ממה שחייבים (כי דיסק נוסף שכושל עלול לדפוק את כל הנתונים).
אז נאלצתי להחליף את הדיסק.
הקטע הכי קשה הוא להבין איזה מהדיסקים הפיזיים צריך לנתק.
בפעם האחרונה דאגתי לסמן את הדיסקים במדבקות עם השם שלהם במערכת ההפעלה (למשל c8d0 עבור דיסק 1 בקונטרולר 8) אבל משום מה המדבקות היו קצת לא מדוייקות.
בכל מקרה, אחרי קצת ניסוי וטעיה, מצאתי איזה מהדיסקים הוא c8d1 (הדיסק שדווח כלא תקין).
הרצתי את הפקודה הבאה כדי להוציא אותו מהמאגר, וניתקתי אותו פיזית.
[code]
zpool detach storage c8d1
[/code]
חיברתי למחשב אחר כדי לבדוק עם SpinRite (אין לי מסך מחובר לשרת הOpenSolaris).
SpinRite לא הצליח לתקן אבל ראיתי שSMART מדווח שיש לו המון שגיאות חיפוש (seek) וקריאה.

שלחתי למעבדה וקיבלתי דיסק אחר תוך כמה שבועות.
התקנתי את הדיסק במחשב, והרצתי את הפקודה הבאה כדי לחבר אותו חזרה:
[code]
zpool attach storage c9d1 c8d1
[/code]
הפקודה די הפוכה לפקודת הניתוק הקודמת, רק שהיא מציינת לאיזה דיסק קיים להוסיף את החדש.
אחרי שהוספתי, זה המצב של המאגר storage :
אפשר לראות שמתבצעת פעולת resilver, שבעצם דואגת לסנכרן הנתונים של הדיסק החדש עם הדיסק הוותיק באותה יחידת ראי.

root@iron:~# zpool status storage
pool: storage
state: ONLINE
status: One or more devices is currently being resilvered. The pool will
continue to function, possibly in a degraded state.
action: Wait for the resilver to complete.
scrub: resilver in progress for 0h14m, 0.60% done, 38h34m to go
config:

NAME STATE READ WRITE CKSUM
storage ONLINE 0 0 0
mirror-0 ONLINE 0 0 0
c8d0p2 ONLINE 0 0 0
c9d0p2 ONLINE 0 0 0
mirror-1 ONLINE 0 0 0
c9d1 ONLINE 0 0 0
c8d1 ONLINE 0 0 0 6.70G resilvered
mirror-2 ONLINE 0 0 0
c11d0 ONLINE 0 0 0
c10d0 ONLINE 0 0 0

errors: No known data errors

Facebook Comments

סקלה

בפוסט הקודם דיברתי על הצורך שלי בצפיה של סרטוני יוטיוב על האייפד כשאני לא מחובר, ועל הפתרון שלי – שמבוסס על כמה סקריפטים שקושרים ביחד את הלוגיקה הבאה:

  1. הבא מהAPI של יוטיוב פיד RSS רלוונטי
  2. חלץ ממנו URLים של סרטונים
  3. הורד את הסרטונים עצמם מיוטיוב עם youtube-dl
  4. קודד את הסרטונים לפורמט של אייפד עם HandBreak
  5. צור פיד RSS שמאפשר להוריד את הקבצים המקודדים

ברגע שהתהליך הזה קיים, מה שנשאר זה לרשום את הפיד החדש בITunes, ומאותו רגע הוא מוריד אוטומטית את הסרטונים לצפייה אוף ליין.

לאחרונה שמעתי יותר ויותר על סקלה, סקלה היא שפה יחסית חדשה (הופיעה ב2003).
אז מה התכונות של סקלה?

  • שפה סטטית, הקומפיילר יגיד לכם כשיש לכם שגיאות טיפוס (בניגוד לשפות דינמיות כמו PHP, פייתון וכו')
  • מבוססת על הJVM, סקלה מתקמפלת לבייט-קוד
  • מאפשרת גישה מלאה ונוחה לתשתיות וספריות של ג'אווה. אם יש לכם קוד ג'אווה תוכלו להשתמש בו בקלות מסקלה
  • הקוד בסקלה הוא הרבה יותר קומפקטי מג'אווה
  • תומכת בתכנות פונקציונלי, פונקציות הן אזרחיות מהשורה הראשונה, אפשר להעביר פונקציות כפרמטר וליצור פונקציות אנונימות בצורה מאוד אלגנטית.
  • תומכת במודל הActors, שהוא מודל שבו "שחקנים" שונים מתקשרים אחד עם השני על ידי שליחת הודעות, ולא חולקים שום State. המודל הזה חוסך לא מעט בעיות שנובעות ממיקבול גבוה
  • תמיכה בסקריפטים, אפשר להריץ קוד כסקריפט בלי לקמפל אותו מראש (למרות שכמובן אפשר לקמפל).

יש עוד כמה תכונות מעניינות לסקלה, אבל אני לא באמת מכיר את השפה לעומק עדיין.

החלטתי ללמוד סקלה, וכדי לעשות את זה כתבתי מחדש את הסקריפטים הקודמים כתוכנית סקלה אחת  וגם שיפרתי אותם על הדרך לתמוך בכמה ערוצי יוטיוב.
עשיתי את זה עם vim, ישירות על השרת – ובלי לקמפל, ממש כמו כתיבה של סקריפט.
אני בטוח שדוגמאות הקוד שלי יכולות להיות יותר אלגנטיות, אבל זה מה שיש לבינתיים 🙂
אז זה הmain של הסקריפט:


object YoutubeRSS
{
val downloader = new Downloader
def main(args: Array[String])
{
deleteOldFiles
regenerateFeeds
Source.fromFile("channels").getLines.foreach(x =>; process(x trim) )
downloader ! Stop
}
//...
}

התוכנית מפעילה כמה פונקציות ואז קוראת שורות מקובץ,  ועל כל שורה מפעילה את הפונקציה process.
לבסוף היא שולחת הודעת Stop לActor של הDownloader.
אפשר לשים מיד לב לכמה דברים:

  • כשמצהירים על object, זה בעצם סינגלטון.
  • אין נקודה-פסיק בסוף שורה, בסקלה זה אופציונלי.
  • לא חייבים להשתמש בסוגריים כשקוראים לפונקציה בלי פרמטרים (למעשה, אם יש לפונקציה פרמטר בודד – אפשר בדרך כלל לוותר על הסוגריים גם אז)
  • בסקלה כמעט כל דבר חוקי כשם של פונקציה, סימן קריאה היא פונקציה די נפוצה בהרבה ספריות.

הנה עוד קצת מהתוכנית:


object Conf
{
val YOUTUBE_CHANNEL_URL = "http://gdata.youtube.com/feeds/api/users/%s/uploads"
val YOUTUBE_PROFILE_URL = "http://gdata.youtube.com/feeds/api/users/%s"
val BIN_DIR = "/home/omry/youtube-rss-2/bin"
val DOWNLOAD_DIR = "/home/omry/youtube-rss-2/download"
val RSS_WEB_DIR="/home/omry/www/youtube-rss.firefang.net"
val RSS_BASE_URL="http://youtube-rss.firefang.net"
val DELETE_OLDER=14
}

object YoutubeRSS
{
//...
def process(channel : String)
{
println("Processing channel " + channel)
updateFeed(channel)
val url = Conf.YOUTUBE_CHANNEL_URL.format(channel)
val rss = Source.fromURL(url).mkString
val xml = XML.loadString(rss)
val entries = xml\"entry";
entries foreach(download(_,channel))
println("Done processing " + channel)
}
}

הקונפיגורציה כרגע היא ישירות בקוד בתוך אובייקט Conf.
שימו לב לאלגנטיות של הבאת הנתונים מהURL ובעיקר של פרסור הXML,
השורה הפשוטה

val entries = xml\"entry";

מחזירה רשימה של entries מתוך הXML (שהוא פיד רסס).

עוד קצת קוד מהפונקציה שמכינה את קובץ הRSS הסופי:


new File(dir).listFiles.
sortBy(_.lastModified).reverse.
filter(_.getName().endsWith(".mp4")).
map(file => file.substring(dir.length + 1)).
foreach(file =>
{
val f = new File(dir,file)
val entry = new SyndEntryImpl()
//..

הקוד פה מקבל (עם API של ג'אווה) את רשימת הקבצים בספריה, ממיין אותן לפי תאריך שינוי, הופך את הסדר, מפעיל פילטר שמשאיר רק קבצי mp4, מפעיל פוקציית map שמורידה את הספריה משם הקובץ ולבסוף מפעיל קוד כל סטרינג.

קצת על הפעלת תהליכים חיצוניים:
מי שניסה להפעיל פרוסס חיצוני מג'אווה בוודאי זוכר שזו לא חוויה מרנינה, צריך שני Threadים שיקראו את הפלט של התהליך (אחד לפלט סטנדרטי ואחד לשגיאה סטנדרטי) ובאופן כללי זה די מסורבל.
בסקלה (2.9 ומעלה) זה הרבה יותר פשוט:

// Run process
"ls -l"!
// Run process and get output into string:
val output = "ls -l"!!
// Run process from a list of arguments
val fname = List("youtube-dl", "--get-filename","-o","download/"+channel+"/%(uploader)s-%(stitle)s.%(ext)s",link.toString)!!

הרבה יותר נחמד מבג'אווה.

אפשר להוריד את הכל מפה.

בשורה התחתונה, בינתיים אני אוהב את סקלה, ונראה לי שאני אתחיל להשתמש בה באופן יותר קבוע.

Facebook Comments

איך להוריד סרטי יוטיוב לצפיה אופליין באייפד/אייפון

כן, אני יודע שמזמן לא היה פה פוסט, תתבעו אותי ;).

אחד הדברים שאף פעם לא עובד כמו שצריך באייפון ובאייפד (ובאייפוד) זה YouTube. כלומר – יש ישום של אפל לצורך העניין, אבל החוויה לא מרנינה:
בדרך כלל לא תצליחו לראות סרטון בלי גמגומים, גם אם אתם משתמשים ברשת אלחוטית מהירה עם חיבור אינטרנט שמן וטוב.

לאחרונה אני צופה בדי הרבה משחקי סטארקרפט ביוטיוב, והפריע לי חוסר היכולת לראות את המשחקים על האייפד.
זה הביא לי רעיון:
מה אם נוריד מראש את הסרטונים, וניתן אותם לאייפד בצורת פיד RSS – ממש כמו שצופים בפוקסט וידאו – ככה שנוכל לראות אותם בלי להיות תלויים בחסדי מהירות התקשורת ליוטיוב?

אז בתור התחלה, הצצה למטרה של כל הפוסט הזה:
הנה פיד הרסס של הערוצים שאני מנוי עלהם ביוטיוב (Subscriptions).
את הפיד הזה הוספתי לאייטונז (כפודקאסט), ומאותו רגע הוא מוריד את הסרטונים ומסנכרן אותם לאייפד.

אז איך כל זה קורה?

הכל מתחיל בAPI הנתונים של יוטיוב, שמאפשר קבלת פידי RSS שונים ומשונים, כשהשימושי שבהם מבחינתי הוא פיד שמאפשר קבלת הסרטונים החדשים בערוצים שמשתמש מסויים מנוי עליהם:

http://gdata.youtube.com/feeds/base/users/omryy/newsubscriptionvideos

שימו לב ששם המשתמש שלי הוא חלק מהURL.
קל למדי לחלץ מפה את רשימת הURLים לסרטונים שמופיעים בפיד, למשל עם הסקריפט הזה:
[CODE]
GET "http://gdata.youtube.com/feeds/base/users/omryy/newsubscriptionvideos" | grep http://www.youtube.com/watch?v=[a-zA-Z0-9_\-]* -o | sort -u
[/CODE]
הסקריפט, בפשטות – מוריד את הפיד, מחפש בתוכו URLים שנראים כמו לינקים לסרטון ביוטיוב, ומבטל כפילויות שמופיעות מסיבה לא חשובה בפיד.

הצעד הבא הוא להוריד את הסרטים עצמם (הלינקים בפיד הם לינקים לצפיה ישירות ביוטיוב).
לשם כך נשתמש בכלי החביב במיוחד, youtube-dl,

הפקודה הבאה תוריד את הסרטון מהURL הנתון, ותשמור אותו בפורמט נחמד וברור שכולל את השם של מי שהעלה, וכן שם קובץ של הכותרת של הסרטים.
youtube-dl -w YOUTUBE_URL -o "%\(uploader\)s-%\(stitle\)s.%\(ext\)s"
אפשר לחבר את הפקודה הזו לפקודה שמחלצת את הURLים מהפיד תוך שימוש בxargs, אבל בחרתי להשתמש בGNU Parallel שמאפשר הפעלה של פקודה מסויימת כמה פעמים במקביל (בדומה מאוד לxargs, רק במקביל).

אז הפקודה השלמה להורדת הסרטונים שמעניינים אותי היא:
[CODE]
#!/bin/bash
source conf
pushd $YT_DOWNLOAD_DIR
GET "http://gdata.youtube.com/feeds/base/users/omryy/newsubscriptionvideos" | grep http://www.youtube.com/watch?v=[a-zA-Z0-9_\-]* -o | sort -u | $BIN_DIR/parallel -j $CONCURRENT_DOWNLOADS $BIN_DIR/youtube-dl -w {} -o "%\(uploader\)s-%\(stitle\)s.%\(ext\)s"
popd
[/CODE]

הסקריפט הזה (והבאים אחריו) משתמש בקובץ ההגדרות הבא:
[CODE]
BIN_DIR=/home/omry/youtube-rss/bin
YT_DOWNLOAD_DIR=/home/omry/youtube-rss/download
CONCURRENT_DOWNLOADS=20
RSS_WEB_DIR=/home/omry/www/youtube-rss.firefang.net
RSS_BASE_URL=http://youtube-rss.firefang.net

# Delete older than X days
DELETE_OLDER=14
[/CODE]
עד פה, הכל טוב.
הפקודה הזו תוריד רק קבצים חדשים שלא הורדו כבר (הדגל -w).

כשניסיתי להעלות לאייפד את הקבצים האלו, שמתי לב שהוא החליט לדלג על חלק גדול מהם.
מסתבר שכל הקבצים שדולגו היו ברזולוציה של HD מלא (1920X1080).
אוקיי, אז זה הדבר הבא שצריך לתקן.
בהתחלה חשבתי להשתמש בffmpeg – שהיא הפתרון המקובל לקידוד קבצי וידאו.
הבעיה היא שצריך לקמפל גרסא שלה שתומכת בH264 (הקידוד שמתאים למכשירי אפל), ושבאופן כללי די קשה לגרום לה לעשות מה שאתם רוצים.
אחרי המשך חפירות מצאתי את Handbreak שהיא תוכנה (בקוד פתוח כמו כל שאר הדברים בפוסט הזה) שתפקידה בחיים הוא לקודד סרטונים לאייפוד, אייפון אייפד ושאר חברים בצורה מאוד פשוטה.
Handbreak מגיעה עם ממשק GTK שלא ממש עניין אותי, ועם שורת פקודה שמאוד עניינה אותי.
אגב, היא משתמשת בlibavcodec מבית היוצר של מפתחי ffmpeg.
לעניינינו, הפקודה לקידוד סרטון לפורמט אייפד היא :
[CODE]
HandBrakeCLI -Z iPad -i input_file -o output_file.mp4
[/CODE]
לא יכול להיות יותר פשוט מזה.

התהליך עצמו יכול לקחת די הרבה זמן, תלוי באורך הסרט ובעוצמת העיבוד של המחשב שלכם.
Handbreak מספיק מוצלחת כדי להשתמש בכל הליבות שלכם (אפשר לומר לה להשתמש במספר מסויים אם רוצים) ולרוץ בעדיפות נמוכה כדי לא לחנוק תהליכים אחרים.

השתמשתי בתסריט bash קטן כדי לעבור על הקבצים שירדו, ולקודד לספריה חדשה קבצים עם אותו שם אם הם כבר לא נמצאים שם.
[CODE]
$ cat encode_new.sh
#!/bin/bash
source conf
for file in `ls -1 $YT_DOWNLOAD_DIR/*.mp4 $YT_DOWNLOAD_DIR/*.flv $YT_DOWNLOAD_DIR/*.video`
do
base=`basename ${file%.*}`
ipad=${RSS_WEB_DIR}/${base}.mp4
if [ -f $ipad ]
then
echo "Skipping $ipad"
else
$BIN_DIR/HandBrakeCLI -Z iPad -i $file -o $ipad
touch -c -r $file $ipad
./update_feed.sh
fi
done
[/CODE]

אחרי כל קובץ שמקודד, הסקיפט update_feed.sh נקרא.
סקריפט זה משתמש בתוכנית ג'אווה קטנה שכתבתי שמכינה קובץ RSS על בסיס ספריה עם סרטוני וידאו.
לבסוף, נשאר רק למחוק קבצים ישנים יותר מ14 יום כדי למנוע התפוצצות, ולהריץ את כל העסק פעם ביום או משהו עם CRON.

[CODE]
$ cat delete_old.sh
#!/bin/bash
source conf
find -mtime +$DELETE_OLDER -exec echo rm $YT_DOWNLOAD_DIR/{} $RSS_WEB_DIR/{} \;
[/CODE]

אפשר להוריד את כל העסק מפה, שימו לב שזה כולל את HandBreak בגרסאת AMD64 ללינוקס, אבל אפשר להוריד מהאתר שלהם גרסאות אחרות.

לסיום, הנה סרטון סטרקראפט מצחיק:

Facebook Comments

ג'אווה וHTTPS

אחד הכאבים המפתיעים והלא צפויים שנתקלתי בהם לאחרונה עם ג'אווה היה כשניסיתי לתקשר עם שרת HTTPS שנחתם בחתימה של StartSSL.
מסתבר שג'אווה מגיעה כמעט בלי חתימות של ספקי חתימות (verisign שם, אבל הרבה מאוד אחרים לא).
חפירות באינטרנט הובילו לכל מני פתרונות שלא עבדו, אולי כי החתימה שלי היא Wildcard certificate (*.site.com).
פתרון אפשרי הוא ליבא את חתימת השורש של StartSSL לתוך הJVM, אבל זו פעולה ידנית שכל משתמש צריך לעשות ובכל מקרה היא לא עבדה לי, אולי בגלל סוג החתימה.
כל זה מעצבן למדי, הדפדפן סומך על האתר אבל ג'אווה לא מסכימה להתחבר:
נסיון להשתמש בURL הרגיל של ג'אווה כדי להתחבר בHTTPS בדרך כלל מוביל לשגיאה הנפלאה הבאה (אלא אם מדובר בחתימה שחתם השורש שלה ידוע לJVM):

Caused by: sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
at sun.security.validator.PKIXValidator.doBuild(PKIXValidator.java:294)
at sun.security.validator.PKIXValidator.engineValidate(PKIXValidator.java:200)
at sun.security.validator.Validator.validate(Validator.java:218)
at com.sun.net.ssl.internal.ssl.X509TrustManagerImpl.validate(X509TrustManagerImpl.java:126)
at com.sun.net.ssl.internal.ssl.X509TrustManagerImpl.checkServerTrusted(X509TrustManagerImpl.java:209)
at com.sun.net.ssl.internal.ssl.X509TrustManagerImpl.checkServerTrusted(X509TrustManagerImpl.java:249)
at com.sun.net.ssl.internal.ssl.ClientHandshaker.serverCertificate(ClientHandshaker.java:1053)
... 16 more
Caused by: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
at sun.security.provider.certpath.SunCertPathBuilder.engineBuild(SunCertPathBuilder.java:174)
at java.security.cert.CertPathBuilder.build(CertPathBuilder.java:238)
at sun.security.validator.PKIXValidator.doBuild(PKIXValidator.java:289)

פתרון:
מעבר לHTTPClient של אפאצ'י ושימוש בקוד הבא (דוגמא לGET ולPOST)
התיעוד של הספריה לא משהו, יש כמה גרסאות לא תואמות שלה והתיעוד מתייחס לגרסא ישנה.
הקוד הבא עובד עם גרסא 4.0.3 של HTTPClient ו4.1.0 של HTTPCore (זו תלות נדרשת לHTTPClient, אפשר להוריד מאותו אתר).
קחו בחשבון שהפתרון הזה מאפשר תקיפת MAN IN THE MIDDLE כדי לזייף את החתימה, אבל מבחינה פרקטית עדיף משהו שעובד אבל קצת פגיע מאשר משהו שלא עובד בכלל. (וכמובן ששימוש בHTTPS הוא עדיף על שימוש בHTTP נקי בכל מקרה).

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;

import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;

import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.auth.AuthScope;
import org.apache.http.auth.UsernamePasswordCredentials;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.conn.ClientConnectionManager;
import org.apache.http.conn.scheme.Scheme;
import org.apache.http.conn.scheme.SchemeRegistry;
import org.apache.http.conn.ssl.SSLSocketFactory;
import org.apache.http.entity.InputStreamEntity;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.params.CoreConnectionPNames;
import org.apache.http.util.EntityUtils;

public class HTTPClientSSLExample
{
public static void main(String[] args) throws MalformedURLException, IOException
{
byte[] bytes = getURLBytes_httpclient("https://www.startssl.com/", 30000, 30000);
System.out.println(new String(bytes));
}

public static byte[] getURLBytes_httpclient(String url, int connectionTimeout, int readTimeout) throws IOException
{
long now = System.currentTimeMillis();

DefaultHttpClient httpclient = getHttpClient(url);

httpclient.getParams().setIntParameter(CoreConnectionPNames.SO_TIMEOUT, readTimeout);
httpclient.getParams().setIntParameter(CoreConnectionPNames.CONNECTION_TIMEOUT, connectionTimeout);

HttpGet get = new HttpGet(url);
HttpResponse response = httpclient.execute(get);
int res = response.getStatusLine().getStatusCode();
if (res == 200)
{
HttpEntity entity = response.getEntity();
int len = (int) entity.getContentLength();
InputStream in = entity.getContent();
ByteArrayOutputStream bout = new ByteArrayOutputStream(len > 0 ? len : 1000);
pump(in, bout);
return bout.toByteArray();
}
else
{
String bs = "";
try
{
HttpEntity entity = response.getEntity();
bs = entity == null ? null : EntityUtils.toString(entity);
}
catch (IOException e)
{
bs += " || Exception while trying to read data from stream : " + e.getMessage();
}

throw new IOException("Server returned HTTP " + res + " after " + (System.currentTimeMillis() - now) + " ms, URL : " + url + " data: " + bs);
}
}

public static ByteArrayInputStream openInputStream_httpclient(String url, int connectionTimeout, int readTimeout, byte[] postdata) throws IOException
{
if (postdata != null)
{
DefaultHttpClient httpClient = getHttpClient(url);
HttpPost post = new HttpPost(url);
InputStreamEntity reqEntity = new InputStreamEntity(new ByteArrayInputStream(postdata), postdata.length);
reqEntity.setContentType("binary/octet-stream");
post.setEntity(reqEntity);
HttpResponse response = httpClient.execute(post);
int res = response.getStatusLine().getStatusCode();
if (res == 200)
{
HttpEntity entity = response.getEntity();
int len = (int) entity.getContentLength();
InputStream in = entity.getContent();
ByteArrayOutputStream bout = new ByteArrayOutputStream(len > 0 ? len : 1000);
pump(in, bout);
return new ByteArrayInputStream(bout.toByteArray());
}
else
{
throw new IOException("Http response code " + res);
}
}
else
{
byte[] bytes = getURLBytes_httpclient(url, connectionTimeout, readTimeout);
return new ByteArrayInputStream(bytes);
}
}

private static DefaultHttpClient getHttpClient(String url1) throws IOException
{
DefaultHttpClient httpclient = new DefaultHttpClient();

try
{
SSLContext ctx = SSLContext.getInstance("TLS");
X509TrustManager tm = new X509TrustManager()
{

public void checkClientTrusted(X509Certificate[] xcs, String string) throws CertificateException
{
}

public void checkServerTrusted(X509Certificate[] xcs, String string) throws CertificateException
{
}

public X509Certificate[] getAcceptedIssuers()
{
return null;
}
};
ctx.init(null, new TrustManager[]
{
tm
}, null);
SSLSocketFactory ssf = new SSLSocketFactory(ctx);
ssf.setHostnameVerifier(SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER);
ClientConnectionManager ccm = httpclient.getConnectionManager();
SchemeRegistry sr = ccm.getSchemeRegistry();
sr.register(new Scheme("https", ssf, 443));
httpclient = new DefaultHttpClient(ccm, httpclient.getParams());
}
catch (Exception ex)
{
ex.printStackTrace();
return null;
}

// This block handles urls with user:password@server block
URL u = new URL(url1);
String userInfo = u.getUserInfo();
if (userInfo != null)
{
String user;
String password;
int i = userInfo != null ? userInfo.indexOf(':') : -1;
if (i == -1)
{
user = userInfo.substring(0);
password = "";
}
else
{
user = userInfo.substring(0, i);
password = userInfo.substring(i + 1);
}
httpclient.getCredentialsProvider().setCredentials(new AuthScope(u.getHost(), u.getPort()), new UsernamePasswordCredentials(user, password));
}
return httpclient;
}

/**
* Writes the bytes read from the given input stream into the given output
* stream until the end of the input stream is reached. Returns the amount
* of bytes actually read/written.
*/
public static int pump(InputStream in, OutputStream out) throws IOException
{
byte[] buf = new byte[4096];
int count;
int amountRead = 0;

while ((count = in.read(buf)) != -1)
{
out.write(buf, 0, count);
amountRead += count;
}

return amountRead;
}
}

Facebook Comments

העולם כרשת

פאול באטלר – מתמחה בפייסבוק – קנה לו קצת תהילה כשרינדר את הגרף החברתי של פייסבוק במערכת קורדינטות גאוגרפיות לפי המיקום של המשתמשים, כשכל שני חברים מתוך דגימה של עשרה מליון משתמשים אקראיים בפייסבוק מצויירת כקו.
התוצאה מרשימה מאוד, ולמעשה מציירת את המפה של רוב העולם.

(לחצו כדי לקבל תמונה ברזולוציה גבוהה).

Facebook Comments

יום הולדת

הרשימה המקושרת של פוסטי ימי ההולדת שלי ממשיכה להתארך עם הגיעו של יום ההולדת ה33.

היומולדת הפעם הוא בסימן סטרקרפט 2, שאוכל את הזמן הפנוי שלי ודי מונע פוסטים בבלוג לאחרונה.
אני משחק במשחק מרובה המשתתפים כמעט כל יום, ורואה גם לא מעט סרטי תחרויות ביוטיוב.
שימו לב לעוגה הזו שאפה הבחור מפה.

עוגת סטארקרפט

לסיכום, הנה משחק משובח ביותר מתחרות בימים האחרונים, זה המשחק השלישי מתוך חמש של הגרנד פינלה של התחרות, מאנה נגד… נאאמה.

Facebook Comments

בזק בינלאומי – האנטי וירוס שחזר מהכפור

בזק בין לאומי החליטו לצ'פר אותי באנטי וירוס בלי שאני אבקש.

הכל היה טוב ויפה כי על עוד לא ידעתי על זה, עד שהבחנתי בתקלות תקשורת מוזרות. פורטים שהיו לי חסומים בתקשורת החוצה, והקש ששבר את גב הגמל: שגיאה בSVN CHECKOUT מהשרת שלי.

אחרי שבזבזתי איזה שעה בלנסות להבין מה נשבר בשרת וגיליתי שהכל נראה תקין ועובד – בדקתי דרך נטוויז'ן ודרך שרת אחר בחו"ל ובשני במקרים זה עבר.
כאן כבר היה לי ברור שמישהו שבזק בין לאומי אשמים.

ניסיתי לדבר איתם בצ'אט בתמיכה, ולא הגיבו אז התקשרתי.
למזלי נפלתי על נציג שלא הניח שאני לא יודע על מה אני מדבר, הוא שינה את הניתוב שלי כך שאני לא אעבור דרך האנטי-וירוס שלהם, עשיתי ריסטרט למודם והנה זה פלא, הכל בסדר.

בזבזתי עוד עשר דקות בלבטל את האנטי וירוס בשרות הלקוחות (מסתבר שלא שילמתי עליו, זה היה צ'ופר מכל הלב!), ואני מקווה שזה סוף הסאגה הזו.

בקיצור, לקוחות בינ"ל, זהירות.

Facebook Comments

רחוב הסוסים, טרויה

היום בבוקר קיבלתי לתיבת הדואר שלי דיסק DVD שמודפס עליו "האנציקלופדיה האינטראקטיבית".
עכשיו, אני בטוח שאנשים רבים מאלו שקיבלו את הדיסק הזה מכניסים אותו למכונת החלונות שלהם בלי חשש, ומפעילים את מה שיש שם.

חיפוש קצת בגוגל מצא את הלינק הזה מ2008, שטוען שמחיר מחשב בודד עם גישת ROOT ירד ל40 סנט.
אם נניח שמחיר DVD הוא כחצי דולר, ונוסיף עוד כ25 סנט עבור ההפצה של הDVD לתיבות דואר – זה עדיין לא משתלם להפיץ טרויאנים ככה.
אבל זה די גבולי.
מה שכן, זה בהחלט אופציה טובה אם רוצים להשתלט על מחשב של אדם ספציפי, או מחשבים של חברה מסויימת.

זכורה לי התקפה שחברת אבטחה ביצעה על אחד הלקוחות שלה כחלק ממבחן חדירות.
החברה פיזרה כונני USB (דיסק על מפתח) נגועים במגרש החניה של החברה, ונתנה לעובדים הסקרנים לעשות את השאר.
האמת היא שאפילו כתבתי על זה פוסט אי שם ב2006: 15 מתוך 20 כוננים הופעלו.

בבדיקה יסודית יותר של הדיסק, אני רואה שכתוב עליו "The jewish videopedia"' מה שמרמז על כך שהוא מכיל סרטוני שטיפת מוח של שועי עולם.
כשחושבים על זה, זה גם סוג של סוס טרויאני, כזה שמשתלט על המוח.
אני די בטוח שעלות מאמין חדש שווה יותר מ40 סנט, ככה שזה כן משתלם.

Facebook Comments

להצביע בארנק

לפני כמה שבועות xmarks, החברה לסינכרון סימניות הודיעה שהיא סוגרת את שעריה מחוסר כסף.
החברה סיפקה שרות שימושי של סינכרון סימניות בין דפדפנים שונים בחינם, מתוך הנחה שהתבררה כשגויה שהם יצליחו לעשות כסף מבסיס הנתונים של הסימניות.
אני אישית לא השתמשתי בxmarks (ומאז נראה שכל מני דברים קורים שאולי יצילו את החברה), אבל האפיזודה הזו גרמה לי לחשוב:

יש כמה שירותים חינמיים שאני כן משתמש בהם, ואני די אתעצב אם הם יופסקו פתאום.
שרות כזה לדוגמא הוא lastpass, שמשמש אותי נאמנה כבר כמה חודשים מאז הפוסט על התורכים.

לlastpass יש שירות פרימיום ב12$ לשנה שנותן כל מיני פיצ'רים כמו תמיכה בסלולריים מסויימים וכו'.
למרות שאני לא ממש צריך את הפיצ'רים המיוחדים שמקבלים בפרימיום, החלטתי לשלם על השירות.
הסיבה היא – כמובן – שאני לא רוצה שlastpass ילכו בדרכו של הדודו, או של הxmarks.

אני קורא לכולכם, אם יש שירות שאתם אוהבים – תראו למפתחים את אהבתכם עם כרטיס האשראי או חשבון הפייפאל שלכם.

Facebook Comments